feat(pine): add Pine voice-assistant app package (manifests + icon)
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>
This commit is contained in:
parent
146a3bc738
commit
1730d02003
70
apps/pine-piper/manifest.yml
Normal file
70
apps/pine-piper/manifest.yml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
app:
|
||||||
|
id: pine-piper
|
||||||
|
name: Pine Piper (TTS)
|
||||||
|
version: "2.2.2"
|
||||||
|
description: Wyoming-protocol Piper text-to-speech engine. Internal Pine voice-assistant stack member — gives Home Assistant Assist a natural voice for spoken responses on the PineVoice satellite.
|
||||||
|
category: home
|
||||||
|
|
||||||
|
# Hyphen name matches the runtime references (stack member table / startup
|
||||||
|
# order) + the live container, so on an existing node the orchestrator ADOPTS
|
||||||
|
# the running engine rather than recreating it (downloaded voices under /data
|
||||||
|
# preserved).
|
||||||
|
container_name: pine-piper
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: docker.io/rhasspy/wyoming-piper:2.2.2
|
||||||
|
pull_policy: if-not-present
|
||||||
|
network: archy-net
|
||||||
|
network_aliases: [pine-piper]
|
||||||
|
# The image entrypoint already binds tcp://0.0.0.0:10200; this arg only
|
||||||
|
# picks the voice (mirrors the pine ha-stack.yml compose command).
|
||||||
|
custom_args: ["--voice", "en_GB-alba-medium"]
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- storage: 1Gi
|
||||||
|
|
||||||
|
resources:
|
||||||
|
memory_limit: 512Mi
|
||||||
|
|
||||||
|
security:
|
||||||
|
# cap-drop=ALL is applied by the orchestrator. A plain Python Wyoming server
|
||||||
|
# on an unprivileged port needs no added capabilities.
|
||||||
|
capabilities: []
|
||||||
|
readonly_root: false # downloads the voice into /data on first run
|
||||||
|
no_new_privileges: true
|
||||||
|
network_policy: isolated
|
||||||
|
|
||||||
|
ports:
|
||||||
|
# Published so Home Assistant (on the pasta net) can reach the engine via
|
||||||
|
# host.containers.internal:10200 (the Wyoming integration endpoint).
|
||||||
|
- host: 10200
|
||||||
|
container: 10200
|
||||||
|
protocol: tcp
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /var/lib/archipelago/pine-piper
|
||||||
|
target: /data
|
||||||
|
options: [rw]
|
||||||
|
|
||||||
|
environment: []
|
||||||
|
|
||||||
|
health_check:
|
||||||
|
type: tcp
|
||||||
|
endpoint: localhost:10200
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s # first start downloads the voice
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
author: Rhasspy / Home Assistant
|
||||||
|
icon: /assets/img/app-icons/pine.svg
|
||||||
|
website: https://github.com/rhasspy/wyoming-piper
|
||||||
|
repo: https://github.com/rhasspy/wyoming-piper
|
||||||
|
license: MIT
|
||||||
|
tags:
|
||||||
|
- home
|
||||||
|
- voice
|
||||||
|
- text-to-speech
|
||||||
|
- wyoming
|
||||||
70
apps/pine-whisper/manifest.yml
Normal file
70
apps/pine-whisper/manifest.yml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
app:
|
||||||
|
id: pine-whisper
|
||||||
|
name: Pine Whisper (STT)
|
||||||
|
version: "3.4.1"
|
||||||
|
description: Wyoming-protocol faster-whisper speech-to-text engine. Internal Pine voice-assistant stack member — turns speech captured by a PineVoice satellite into text for Home Assistant Assist.
|
||||||
|
category: home
|
||||||
|
|
||||||
|
# Hyphen name matches the runtime references (stack member table / startup
|
||||||
|
# order) + the live container, so on an existing node the orchestrator ADOPTS
|
||||||
|
# the running engine rather than recreating it (downloaded models under /data
|
||||||
|
# preserved).
|
||||||
|
container_name: pine-whisper
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: docker.io/rhasspy/wyoming-whisper:3.4.1
|
||||||
|
pull_policy: if-not-present
|
||||||
|
network: archy-net
|
||||||
|
network_aliases: [pine-whisper]
|
||||||
|
# The image entrypoint already binds tcp://0.0.0.0:10300; these args only
|
||||||
|
# pick the model + language (mirrors the pine ha-stack.yml compose command).
|
||||||
|
custom_args: ["--model", "base-int8", "--language", "en"]
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- storage: 2Gi
|
||||||
|
|
||||||
|
resources:
|
||||||
|
memory_limit: 2Gi
|
||||||
|
|
||||||
|
security:
|
||||||
|
# cap-drop=ALL is applied by the orchestrator. A plain Python Wyoming server
|
||||||
|
# on an unprivileged port needs no added capabilities.
|
||||||
|
capabilities: []
|
||||||
|
readonly_root: false # downloads the whisper model into /data on first run
|
||||||
|
no_new_privileges: true
|
||||||
|
network_policy: isolated
|
||||||
|
|
||||||
|
ports:
|
||||||
|
# Published so Home Assistant (on the pasta net) can reach the engine via
|
||||||
|
# host.containers.internal:10300 (the Wyoming integration endpoint).
|
||||||
|
- host: 10300
|
||||||
|
container: 10300
|
||||||
|
protocol: tcp
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /var/lib/archipelago/pine-whisper
|
||||||
|
target: /data
|
||||||
|
options: [rw]
|
||||||
|
|
||||||
|
environment: []
|
||||||
|
|
||||||
|
health_check:
|
||||||
|
type: tcp
|
||||||
|
endpoint: localhost:10300
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s # first start downloads the model
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
author: Rhasspy / Home Assistant
|
||||||
|
icon: /assets/img/app-icons/pine.svg
|
||||||
|
website: https://github.com/rhasspy/wyoming-faster-whisper
|
||||||
|
repo: https://github.com/rhasspy/wyoming-faster-whisper
|
||||||
|
license: MIT
|
||||||
|
tags:
|
||||||
|
- home
|
||||||
|
- voice
|
||||||
|
- speech-to-text
|
||||||
|
- wyoming
|
||||||
177
apps/pine/manifest.yml
Normal file
177
apps/pine/manifest.yml
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
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
|
||||||
11
neode-ui/public/assets/img/app-icons/pine.svg
Normal file
11
neode-ui/public/assets/img/app-icons/pine.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Pine">
|
||||||
|
<rect width="512" height="512" fill="#ffffff"/>
|
||||||
|
<g fill="#000000">
|
||||||
|
<!-- trunk -->
|
||||||
|
<rect x="236" y="396" width="40" height="72" rx="6"/>
|
||||||
|
<!-- three tiers of the evergreen, top to bottom -->
|
||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 508 B |
@ -39,6 +39,8 @@ INTERNAL_MANIFEST_IDS = {
|
|||||||
"indeedhub-relay",
|
"indeedhub-relay",
|
||||||
"netbird-dashboard",
|
"netbird-dashboard",
|
||||||
"netbird-server",
|
"netbird-server",
|
||||||
|
"pine-whisper",
|
||||||
|
"pine-piper",
|
||||||
}
|
}
|
||||||
|
|
||||||
LEGACY_STACK_CATALOG_IDS = {
|
LEGACY_STACK_CATALOG_IDS = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user