# FlowerCore TTS Reader — Text-to-speech book reader service --- apiVersion: v1 kind: Namespace metadata: name: fc-ttsreader labels: app.kubernetes.io/part-of: flowercore --- # 1Password -> K8s Secret sync for TTS Reader API keys apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: ttsreader-secrets namespace: fc-ttsreader spec: itemPath: "vaults/IAmWorkin/items/FlowerCore TTS Reader" --- apiVersion: apps/v1 kind: Deployment metadata: name: ttsreader-piper namespace: fc-ttsreader labels: app.kubernetes.io/name: ttsreader-piper app.kubernetes.io/part-of: flowercore spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: ttsreader-piper template: metadata: labels: app.kubernetes.io/name: ttsreader-piper app.kubernetes.io/part-of: flowercore spec: initContainers: - name: seed-voices image: rhasspy/wyoming-piper:latest command: - python3 - -c args: - | import shutil import ssl from pathlib import Path from urllib.request import urlopen ssl._create_default_https_context = ssl._create_unverified_context files = { "en_US-lessac-high.onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/high/en_US-lessac-high.onnx", "en_US-lessac-high.onnx.json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/high/en_US-lessac-high.onnx.json", "en_US-lessac-medium.onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx", "en_US-lessac-medium.onnx.json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json", "en_US-amy-medium.onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/medium/en_US-amy-medium.onnx", "en_US-amy-medium.onnx.json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/amy/medium/en_US-amy-medium.onnx.json", "en_US-john-medium.onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/john/medium/en_US-john-medium.onnx", "en_US-john-medium.onnx.json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/john/medium/en_US-john-medium.onnx.json", "en_GB-cori-high.onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/cori/high/en_GB-cori-high.onnx", "en_GB-cori-high.onnx.json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_GB/cori/high/en_GB-cori-high.onnx.json", } target = Path("/data") target.mkdir(parents=True, exist_ok=True) for name, url in files.items(): path = target / name if path.exists() and path.stat().st_size > 0: print(f"cached {name}", flush=True) continue print(f"downloading {name}", flush=True) with urlopen(url, timeout=180) as response, open(path, "wb") as download_file: shutil.copyfileobj(response, download_file) print(f"ready {name}", flush=True) volumeMounts: - name: data mountPath: /data containers: - name: piper image: rhasspy/wyoming-piper:latest env: - name: PYTHONHTTPSVERIFY value: "0" args: - "--voice" - "en_US-lessac-high" - "--data-dir" - "/data" - "--download-dir" - "/data" ports: - containerPort: 10200 name: wyoming resources: requests: cpu: 250m memory: 256Mi limits: cpu: 1000m memory: 1Gi volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: ttsreader-piper-data --- # fc-speech-align — cluster-native faster-whisper wrapper. # Exposes POST /align (fc-align contract used by FlowerCore.Shared.Speech) AND # POST /transcribe (audio-file-in feature). CPU model = base.en, int8 compute. # Source: bluejay-infra/apps/fc-ttsreader/speech-align/ (Dockerfile + app.py). apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ttsreader-align-models namespace: fc-ttsreader spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: ttsreader-align namespace: fc-ttsreader labels: app.kubernetes.io/name: ttsreader-align app.kubernetes.io/part-of: flowercore spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: ttsreader-align template: metadata: labels: app.kubernetes.io/name: ttsreader-align app.kubernetes.io/part-of: flowercore spec: # Bypass CoreDNS's *.iamworkin.lan template hijack on public hosts # (huggingface.co model download at first boot would otherwise resolve # to Traefik VIP via search expansion). Drops the iamworkin.lan suffix. dnsPolicy: None dnsConfig: nameservers: - 10.43.0.10 searches: - fc-ttsreader.svc.cluster.local - svc.cluster.local - cluster.local options: - name: ndots value: "2" securityContext: fsGroup: 1654 runAsNonRoot: true runAsUser: 1654 containers: - name: align image: localhost/fc-speech-align:v2 imagePullPolicy: Never ports: - containerPort: 9200 name: http env: - name: WHISPER_MODEL value: "Systran/faster-whisper-base.en" - name: WHISPER_DEVICE value: "cpu" - name: WHISPER_COMPUTE_TYPE value: "int8" - name: WHISPER_CACHE_DIR value: "/models" - name: DEFAULT_LANGUAGE value: "en" resources: requests: cpu: 250m memory: 512Mi limits: cpu: 2000m memory: 2Gi volumeMounts: - name: models mountPath: /models readinessProbe: httpGet: path: /health port: 9200 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 18 livenessProbe: httpGet: path: /health port: 9200 initialDelaySeconds: 180 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 volumes: - name: models persistentVolumeClaim: claimName: ttsreader-align-models --- apiVersion: v1 kind: Service metadata: name: ttsreader-align namespace: fc-ttsreader spec: selector: app.kubernetes.io/name: ttsreader-align ports: - port: 9200 targetPort: 9200 name: http --- apiVersion: apps/v1 kind: Deployment metadata: name: ttsreader-web namespace: fc-ttsreader labels: app.kubernetes.io/name: ttsreader-web app.kubernetes.io/part-of: flowercore spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: ttsreader-web template: metadata: labels: app.kubernetes.io/name: ttsreader-web app.kubernetes.io/part-of: flowercore annotations: prometheus.io/scrape: "true" prometheus.io/port: "5217" prometheus.io/path: "/metrics" spec: securityContext: fsGroup: 1654 fsGroupChangePolicy: OnRootMismatch containers: - name: web image: localhost/fc-ttsreader-web:v202604251046 imagePullPolicy: Never ports: - containerPort: 5217 name: http env: - name: ASPNETCORE_URLS value: "http://+:5217" - name: ASPNETCORE_ENVIRONMENT value: "Production" - name: FlowerCore__Database__ConnectionStrings__Sqlite value: "Data Source=/data/ttsreader.db" - name: TtsReader__Audio__OutputRoot value: "/data/audio" - name: TtsReader__Audio__FfmpegPath value: "/usr/bin/ffmpeg" - name: TtsReader__Bible__CorpusRoot value: "/data/corpus-cache/world-english-bible/eng/usx" - name: TtsReader__Jobs__Root value: "/data/jobs" - name: TtsReader__Piper__Host value: "ttsreader-piper.fc-ttsreader.svc.cluster.local." - name: TtsReader__Piper__Port value: "10200" - name: TtsReader__Kokoro__Enabled value: "true" - name: TtsReader__Kokoro__BaseUrl value: "http://10.0.56.20:10401" - name: TtsReader__Kokoro__TimeoutSeconds value: "120" - name: Speech__Alignment__Enabled # Cluster-native faster-whisper (Lane F, 2026-04-25). The # ttsreader-align deployment in this manifest wraps # SYSTRAN/faster-whisper with a /align endpoint matching the # FlowerCore.Shared.Speech master contract. value: "true" - name: Speech__Alignment__BaseUrl value: "http://ttsreader-align.fc-ttsreader.svc.cluster.local.:9200" - name: Speech__Alignment__TimeoutSeconds value: "120" # Cluster-native transcription endpoint shares the same pod # (POST /transcribe). Lane G consumes this from the # FlowerCore.TtsReader.Web AudioImport feature. - name: TtsReader__Transcription__Enabled value: "true" - name: TtsReader__Transcription__BaseUrl value: "http://ttsreader-align.fc-ttsreader.svc.cluster.local.:9200" - name: TtsReader__Transcription__TimeoutSeconds value: "300" - name: TtsReader__Ollama__BaseUrl value: "http://10.0.57.17:11434" - name: TtsReader__Ollama__DefaultModel value: "gemma3:4b" - name: TtsReader__Ollama__TimeoutSeconds value: "45" - name: TtsReader__Runtime__LogsRoot value: "/data/logs" - name: TtsReader__Runtime__SmokeStatePath value: "/data/ops/smoke-status.json" - name: Auth__ApiKey valueFrom: secretKeyRef: name: ttsreader-secrets key: Auth__ApiKey optional: true - name: Auth__AdminApiKey valueFrom: secretKeyRef: name: ttsreader-secrets key: Auth__AdminApiKey optional: true resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi volumeMounts: - name: data mountPath: /data - name: tmp mountPath: /tmp securityContext: runAsNonRoot: true runAsUser: 1654 runAsGroup: 1654 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL readinessProbe: httpGet: path: /health port: 5217 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 5217 initialDelaySeconds: 15 periodSeconds: 30 volumes: - name: data persistentVolumeClaim: claimName: ttsreader-data - name: tmp emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: ttsreader-piper namespace: fc-ttsreader spec: selector: app.kubernetes.io/name: ttsreader-piper ports: - port: 10200 targetPort: 10200 name: wyoming --- apiVersion: v1 kind: Service metadata: name: ttsreader-web namespace: fc-ttsreader spec: selector: app.kubernetes.io/name: ttsreader-web ports: - port: 5217 targetPort: 5217 name: http --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ttsreader-piper-data namespace: fc-ttsreader spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 2Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ttsreader-data namespace: fc-ttsreader spec: accessModes: - ReadWriteOnce storageClassName: longhorn resources: requests: storage: 5Gi --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: ttsreader-cert namespace: fc-ttsreader spec: secretName: ttsreader-tls issuerRef: name: step-ca-acme kind: ClusterIssuer dnsNames: - ttsreader.iamworkin.lan --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: ttsreader-web namespace: fc-ttsreader spec: entryPoints: - websecure routes: - match: Host(`ttsreader.iamworkin.lan`) kind: Rule services: - name: ttsreader-web port: 5217 tls: secretName: ttsreader-tls