app: id: pine name: Pine version: "1.3.0" description: A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node — block height, sync, peers, Lightning balance — and, when a Claude API key is set, anything else. 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 "Connect Pine to WiFi" provisioner 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] # The provisioner uses Web Bluetooth (Improv-over-BLE) to push WiFi creds to # the PineVoice speaker. navigator.bluetooth only exists in a SECURE CONTEXT # (https or localhost), so the launcher terminates TLS with a self-signed # cert — otherwise the "Connect Pine to WiFi" button is inert on the LAN. # Idempotent: kept as-is when crt+key already exist. Mirrors the netbird # secure-context fix (#15). generated_certs: - crt: /var/lib/archipelago/pine/tls.crt key: /var/lib/archipelago/pine/tls.key dependencies: - app_id: pine-whisper - app_id: pine-piper - app_id: pine-openwakeword - storage: 128Mi resources: memory_limit: 64Mi security: # cap-drop=ALL is applied by the orchestrator. nginx (master as root, drops # workers) binds :443 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: # 10380 (http) is the Open target — the UI launches apps as # http://host:10380 (resolveAppUrl). nginx there 301-redirects to the https # listener on 10381, so the new tab lands on a secure context where # navigator.bluetooth (the "Connect Pine to WiFi" provisioner) works. - host: 10380 container: 80 protocol: tcp - host: 10381 container: 443 protocol: tcp volumes: - type: bind source: /var/lib/archipelago/pine/nginx.conf target: /etc/nginx/conf.d/default.conf options: [ro] - type: bind source: /var/lib/archipelago/pine/tls.crt target: /etc/nginx/tls.crt options: [ro] - type: bind source: /var/lib/archipelago/pine/tls.key target: /etc/nginx/tls.key options: [ro] - type: bind source: /var/lib/archipelago/pine/index.html target: /usr/share/nginx/html/index.html options: [ro] environment: [] files: - path: /var/lib/archipelago/pine/nginx.conf overwrite: true content: | server { listen 80; server_name _; return 301 https://$host:10381$request_uri; } server { listen 443 ssl; server_name _; ssl_certificate /etc/nginx/tls.crt; ssl_certificate_key /etc/nginx/tls.key; root /usr/share/nginx/html; index index.html; # Live node facts for the status card — proxied to the node's # public status tier so the (https) page can fetch same-origin. location = /node-status { proxy_pass http://host.containers.internal:80/api/pine/status; proxy_http_version 1.1; proxy_set_header Host $host; proxy_connect_timeout 5s; proxy_read_timeout 10s; } location / { try_files $uri $uri/ /index.html; } } - path: /var/lib/archipelago/pine/index.html overwrite: true content: |
Connect your speaker — everything stays on your node.
:10300:10200:10400)After WiFi joins, one manual step remains — pair the speaker in Home Assistant: Settings → Devices & services → Add Wyoming Protocol, host = the speaker’s IP, port 10700. Whisper, Piper, openWakeWord and the Assist pipeline are wired up automatically when Pine installs. Wake word: “Hey Jarvis.” Ask node things like “what’s the block height?”, “how many peers?”, “is the node synced?” or “what’s my lightning balance?” — and when a Claude API key is set on the node, anything else gets answered by Claude. New mesh messages are announced on the speaker too.
Troubleshooting: if it hears you (LED reacts) but answers are silent, unplug and replug the speaker — an interrupted answer can wedge its audio output until it reboots.