Package the PineVoice/Wyoming local voice assistant as ONE store app "Pine" that runs three rootless containers: - pine-whisper — Wyoming faster-whisper STT (docker.io/rhasspy/wyoming-whisper 3.4.1, model base-int8/en), publishes :10300 - pine-piper — Wyoming Piper TTS (2.2.2, voice en_GB-alba-medium), publishes :10200 - pine — nginx launcher serving a self-contained setup/status page (WiFi provisioning + Home Assistant wiring instructions), the Open target The two engines publish their Wyoming ports so Home Assistant reaches them via host.containers.internal. All rootless (cap-drop=ALL, no_new_privileges), manifest-driven — no hardcoded podman/installer. The engines are internal stack members (added to the drift-check INTERNAL_MANIFEST_IDS allowlist); only "pine" carries a catalog entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
178 lines
6.9 KiB
YAML
178 lines
6.9 KiB
YAML
app:
|
|
id: pine
|
|
name: Pine
|
|
version: "1.0.0"
|
|
description: A private voice assistant for your home. Pine runs speech-to-text (Whisper) and text-to-speech (Piper) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud.
|
|
category: home
|
|
|
|
# The user-facing launcher (app_id + container both "pine", matching the
|
|
# runtime references + the live container so the orchestrator adopts it). A
|
|
# tiny nginx that serves the setup / status page for the voice stack. The two
|
|
# Wyoming engines (pine-whisper, pine-piper) are internal stack members.
|
|
container_name: pine
|
|
|
|
container:
|
|
image: docker.io/library/nginx:1.27-alpine
|
|
pull_policy: if-not-present
|
|
network: archy-net
|
|
network_aliases: [pine]
|
|
|
|
dependencies:
|
|
- app_id: pine-whisper
|
|
- app_id: pine-piper
|
|
- storage: 128Mi
|
|
|
|
resources:
|
|
memory_limit: 64Mi
|
|
|
|
security:
|
|
# cap-drop=ALL is applied by the orchestrator. nginx (master as root, drops
|
|
# workers) binds :80 inside the container — needs the worker-drop caps +
|
|
# NET_BIND_SERVICE for the privileged port.
|
|
capabilities: [CHOWN, DAC_OVERRIDE, SETGID, SETUID, NET_BIND_SERVICE]
|
|
readonly_root: false
|
|
no_new_privileges: true
|
|
network_policy: isolated
|
|
|
|
ports:
|
|
- host: 10380
|
|
container: 80
|
|
protocol: tcp
|
|
|
|
volumes:
|
|
- type: bind
|
|
source: /var/lib/archipelago/pine/index.html
|
|
target: /usr/share/nginx/html/index.html
|
|
options: [ro]
|
|
|
|
environment: []
|
|
|
|
# The setup / status page, written to the host before create and served
|
|
# read-only. Web-Bluetooth WiFi provisioning of the speaker only works from a
|
|
# secure/localhost context (not this LAN page), so the page documents that
|
|
# flow rather than embedding it — the live provisioner lives in the `pine`
|
|
# Gitea repo (index.html), run from a laptop on localhost.
|
|
files:
|
|
- path: /var/lib/archipelago/pine/index.html
|
|
overwrite: true
|
|
content: |
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Pine — private voice assistant</title>
|
|
<style>
|
|
:root { color-scheme: dark; }
|
|
body { margin: 0; font: 16px/1.6 system-ui, sans-serif;
|
|
background: #0f1512; color: #e7efe9; }
|
|
.wrap { max-width: 720px; margin: 0 auto; padding: 40px 24px 80px; }
|
|
header { display: flex; align-items: center; gap: 16px; margin-bottom: 8px; }
|
|
header svg { width: 56px; height: 56px; flex: 0 0 auto; }
|
|
h1 { font-size: 28px; margin: 0; }
|
|
.tag { color: #8fbfa5; font-size: 14px; margin: 2px 0 0; }
|
|
h2 { font-size: 18px; margin: 32px 0 8px; color: #b7e0c8; }
|
|
ol { padding-left: 22px; } li { margin: 6px 0; }
|
|
code { background: #1c2620; padding: 2px 6px; border-radius: 5px;
|
|
font-size: 14px; color: #cfe9d9; }
|
|
.card { background: #16201b; border: 1px solid #24332b;
|
|
border-radius: 12px; padding: 16px 20px; margin: 16px 0; }
|
|
.row { display: flex; gap: 12px; align-items: baseline; }
|
|
.row b { min-width: 96px; color: #9fd3b6; }
|
|
a { color: #7fd6a6; }
|
|
footer { margin-top: 40px; color: #6d8578; font-size: 13px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
<header>
|
|
<svg viewBox="0 0 512 512" aria-hidden="true"><g fill="#7fd6a6">
|
|
<rect x="236" y="396" width="40" height="72" rx="6"/>
|
|
<path d="M256 44 L336 168 L296 168 L256 108 L216 168 L176 168 Z"/>
|
|
<path d="M256 150 L360 300 L300 300 L256 236 L212 300 L152 300 Z"/>
|
|
<path d="M256 262 L392 430 L120 430 L256 262 Z"/>
|
|
</g></svg>
|
|
<div>
|
|
<h1>Pine</h1>
|
|
<p class="tag">A private voice assistant that runs on your node.</p>
|
|
</div>
|
|
</header>
|
|
|
|
<p>Pine gives Home Assistant a local voice: your PineVoice speaker
|
|
hears you, <b>Whisper</b> turns speech into text on this node, and
|
|
<b>Piper</b> speaks the reply back — nothing leaves your home.</p>
|
|
|
|
<div class="card">
|
|
<div class="row"><b>Whisper (STT)</b><span>listening on
|
|
<code>host.containers.internal:10300</code></span></div>
|
|
<div class="row"><b>Piper (TTS)</b><span>listening on
|
|
<code>host.containers.internal:10200</code></span></div>
|
|
<div class="row"><b>Speaker</b><span>Wyoming satellite on
|
|
<code><speaker-ip>:10700</code></span></div>
|
|
</div>
|
|
|
|
<h2>1 · Get the speaker on WiFi</h2>
|
|
<ol>
|
|
<li>Web-Bluetooth provisioning needs a secure/localhost page, so
|
|
run the setup tool from the <code>pine</code> repo on a laptop:
|
|
<code>python3 -m http.server 8377 --bind 127.0.0.1</code>, then
|
|
open <code>http://localhost:8377</code> in Chrome.</li>
|
|
<li>Put the speaker in provisioning mode (ring LED blinking
|
|
yellow), enter your WiFi, and press the centre button when the
|
|
page asks for authorization.</li>
|
|
<li>Success: the log shows <code>✓ PROVISIONED</code> and the
|
|
speaker chimes.</li>
|
|
</ol>
|
|
|
|
<h2>2 · Wire it into Home Assistant</h2>
|
|
<ol>
|
|
<li>In Home Assistant: <b>Settings → Devices & services</b>.
|
|
Add the two <b>Wyoming Protocol</b> integrations —
|
|
<code>host.containers.internal:10300</code> (Whisper) and
|
|
<code>:10200</code> (Piper).</li>
|
|
<li>Add the speaker as a third Wyoming device at
|
|
<code><speaker-ip>:10700</code>.</li>
|
|
<li>Build an <b>Assist</b> pipeline: STT = faster-whisper,
|
|
TTS = Piper, then set it as the speaker's pipeline.</li>
|
|
<li>Say the wake word (<b>“Hey Jarvis”</b>) or press the speaker's
|
|
centre button, and talk to your home.</li>
|
|
</ol>
|
|
|
|
<footer>Pine · Whisper + Piper run locally on this Archipelago node.
|
|
No cloud, no account.</footer>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
|
|
health_check:
|
|
type: tcp
|
|
endpoint: localhost:80
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
|
|
interfaces:
|
|
main:
|
|
name: Pine
|
|
description: Set up and check your private voice assistant
|
|
type: ui
|
|
port: 10380
|
|
protocol: http
|
|
path: /
|
|
|
|
metadata:
|
|
author: Archipelago
|
|
icon: /assets/img/app-icons/pine.svg
|
|
website: https://github.com/rhasspy/wyoming
|
|
repo: https://github.com/rhasspy/wyoming
|
|
license: MIT
|
|
category: home
|
|
launch:
|
|
open_in_new_tab: true
|
|
tags:
|
|
- home
|
|
- voice
|
|
- assistant
|
|
- privacy
|