Skip to content

Commit e36444a

Browse files
committed
initial commit
0 parents  commit e36444a

File tree

12 files changed

+385
-0
lines changed

12 files changed

+385
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Snapcast Server for Home Assistant

snapserver/Dockerfile

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
ARG BUILD_FROM=ghcr.io/hassio-addons/base/amd64:9.2.0
2+
# hadolint ignore=DL3006
3+
FROM ${BUILD_FROM}
4+
5+
# Set shell
6+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
7+
8+
# Setup base
9+
RUN apk add --no-cache \
10+
coreutils=8.32-r2 \
11+
wget=1.21.1-r1 \
12+
curl \
13+
cargo \
14+
portaudio-dev \
15+
protobuf-dev
16+
17+
# Add env
18+
ENV LANG C.UTF-8
19+
20+
# Install snapcast
21+
RUN apk add --no-cache snapcast
22+
23+
# Install librespot
24+
RUN cd /root \
25+
&& curl -LO https://github.com/librespot-org/librespot/archive/v0.2.0.zip \
26+
&& unzip v0.2.0.zip \
27+
&& cd librespot-0.2.0 \
28+
&& cargo build --jobs $(grep -c ^processor /proc/cpuinfo) --release --no-default-features \
29+
&& mv target/release/librespot /usr/local/bin \
30+
&& cd / \
31+
&& apk --purge del curl cargo portaudio-dev protobuf-dev \
32+
&& apk add llvm-libunwind \
33+
&& rm -rf /etc/ssl /var/cache/apk/* /lib/apk/db/* /root/v0.2.0.zip /root/librespot-0.2.0 /root/.cargo
34+
35+
# Copy root filesystem
36+
COPY rootfs /
37+
38+
39+
# Build arguments
40+
ARG BUILD_ARCH
41+
ARG BUILD_DATE
42+
ARG BUILD_DESCRIPTION
43+
ARG BUILD_NAME
44+
ARG BUILD_REF
45+
ARG BUILD_REPOSITORY
46+
ARG BUILD_VERSION
47+
48+
# Labels
49+
LABEL \
50+
io.hass.name="${BUILD_NAME}" \
51+
io.hass.description="${BUILD_DESCRIPTION}" \
52+
io.hass.arch="${BUILD_ARCH}" \
53+
io.hass.type="addon" \
54+
io.hass.version=${BUILD_VERSION} \
55+
maintainer="grunjol <[email protected]>" \
56+
org.opencontainers.image.title="${BUILD_NAME}" \
57+
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
58+
org.opencontainers.image.vendor="grunjol Add-ons" \
59+
org.opencontainers.image.authors="grunjol <[email protected]>" \
60+
org.opencontainers.image.licenses="MIT" \
61+
org.opencontainers.image.url="https://github.com/grunjol" \
62+
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
63+
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
64+
org.opencontainers.image.created=${BUILD_DATE} \
65+
org.opencontainers.image.revision=${BUILD_REF} \
66+
org.opencontainers.image.version=${BUILD_VERSION}

snapserver/build.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"build_from": {
3+
"aarch64": "ghcr.io/hassio-addons/base/aarch64:9.2.0",
4+
"amd64": "ghcr.io/hassio-addons/base/amd64:9.2.0",
5+
"armhf": "ghcr.io/hassio-addons/base/armhf:9.2.0",
6+
"armv7": "ghcr.io/hassio-addons/base/armv7:9.2.0",
7+
"i386": "ghcr.io/hassio-addons/base/i386:9.2.0"
8+
}
9+
}

snapserver/config.json

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"name": "Snapcast Server",
3+
"version": "latest",
4+
"slug": "snapserver",
5+
"description": "Snapcast server with librespot support",
6+
"url": "https://github.com/grunjol/addon-snapserver",
7+
"startup": "system",
8+
"boot": "auto",
9+
"map": [
10+
"share:rw"
11+
],
12+
"options": {
13+
"stream": {
14+
"sources": [
15+
"pipe:///share/snapfifo/librespot?name=SpotifyConnect&sampleformat=44100:16:2",
16+
"pipe:///share/snapfifo/mopidy?name=Mopidy&sampleformat=44100:16:2"
17+
],
18+
"buffer": "1000",
19+
"codec": "flac",
20+
"send_to_muted": "false",
21+
"sampleformat": "48000:16:2"
22+
},
23+
"http": {
24+
"enabled": "true",
25+
"doc_root": " "
26+
},
27+
"tcp": {
28+
"enabled": "true"
29+
},
30+
"logging": {
31+
"filter": "*:debug"
32+
},
33+
"server": {
34+
"threads": "-1",
35+
"datadir": "/share/snapcast/"
36+
}
37+
},
38+
"schema": {
39+
"stream": {
40+
"sources": [
41+
"str"
42+
],
43+
"buffer": "int",
44+
"codec": "str",
45+
"send_to_muted": "str",
46+
"sampleformat": "str"
47+
},
48+
"http": {
49+
"enabled": "str",
50+
"doc_root": "str"
51+
},
52+
"tcp": {
53+
"enabled": "str"
54+
},
55+
"logging": {
56+
"filter": "str?"
57+
},
58+
"server": {
59+
"threads": "int",
60+
"datadir": "str"
61+
}
62+
},
63+
"ports": {
64+
"1704/tcp": 1704,
65+
"1705/tcp": 1705,
66+
"1780/tcp": 1780
67+
},
68+
"ports_description": {
69+
"1704/tcp": "Snapcast Server",
70+
"1705/udp": "Snapcast Control",
71+
"1780/tcp": "SnapWeb interface"
72+
},
73+
"arch": [
74+
"armhf",
75+
"armv7",
76+
"aarch64",
77+
"amd64",
78+
"i386"
79+
]
80+
}

snapserver/icon.png

11.8 KB
Loading

snapserver/logo.png

7.63 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/with-contenv bashio
2+
# ==============================================================================
3+
# Ensures needed folders exists.
4+
# ==============================================================================
5+
6+
for SNAP_DIR in /share/snapfifo /share/snapcast
7+
do
8+
if ! bashio::fs.directory_exists "${SNAP_DIR}"; then
9+
mkdir -p "${SNAP_DIR}" || bashio::exit.nok "Could not create folder: ${SNAP_DIR}"
10+
fi
11+
done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/with-contenv bashio
2+
3+
tempio \
4+
-conf /data/options.json \
5+
-template /etc/snapcast/templates/snapserver.gtpl \
6+
-out /etc/snapcast/snapserver.conf
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/execlineb -S0
2+
# ==============================================================================
3+
# Home Assistant Community Add-on: Mopidy
4+
# Take down the S6 supervision tree when Mopidy fails
5+
# ==============================================================================
6+
if -n { s6-test $# -ne 0 }
7+
if -n { s6-test ${1} -eq 256 }
8+
9+
s6-svscanctl -t /var/run/s6/services
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/with-contenv bashio
2+
# ==============================================================================
3+
# Runs the Snapcast Server
4+
# ==============================================================================
5+
declare -a options
6+
7+
bashio::log.info "Starting Snapcast Server..."
8+
9+
/usr/bin/snapserver -c /etc/snapcast/snapserver.conf
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
###############################################################################
2+
# ______ #
3+
# / _____) #
4+
# ( (____ ____ _____ ____ ___ _____ ____ _ _ _____ ____ #
5+
# \____ \ | _ \ (____ || _ \ /___)| ___ | / ___)| | | || ___ | / ___) #
6+
# _____) )| | | |/ ___ || |_| ||___ || ____|| | \ V / | ____|| | #
7+
# (______/ |_| |_|\_____|| __/ (___/ |_____)|_| \_/ |_____)|_| #
8+
# |_| #
9+
# #
10+
# Snapserver config file #
11+
# #
12+
###############################################################################
13+
14+
# default values are commented
15+
# uncomment and edit to change them
16+
17+
# Settings can be overwritten on command line with:
18+
# "--<section>.<name>=<value>", e.g. --server.threads=4
19+
20+
# General server settings #####################################################
21+
#
22+
[server]
23+
# Number of additional worker threads to use
24+
# - For values < 0 the number of threads will be 2 (on single and dual cores)
25+
# or 4 (for quad and more cores)
26+
# - 0 will utilize just the processes main thread and might cause audio drops
27+
# in case there are a couple of longer running tasks, such as encoding
28+
# multiple audio streams
29+
{{ if .server.threads }}
30+
threads = {{ .server.threads }}
31+
{{ end }}
32+
33+
# the pid file when running as daemon
34+
#pidfile = /var/run/snapserver/pid
35+
36+
# the user to run as when daemonized
37+
#user = snapserver
38+
# the group to run as when daemonized
39+
#group = snapserver
40+
41+
# directory where persistent data is stored (server.json)
42+
# if empty, data dir will be
43+
# - "/var/lib/snapserver/" when running as daemon
44+
# - "$HOME/.config/snapserver/" when not running as daemon
45+
{{ if .server.datadir }}
46+
datadir = {{ .server.datadir }}
47+
{{ end }}
48+
#
49+
###############################################################################
50+
51+
52+
# HTTP RPC ####################################################################
53+
#
54+
[http]
55+
# enable HTTP Json RPC (HTTP POST and websockets)
56+
{{ if .http.enabled }}
57+
enabled = {{ .http.enabled }}
58+
{{ end }}
59+
60+
# address to listen on, can be specified multiple times
61+
# use "0.0.0.0" to bind to any IPv4 address or :: to bind to any IPv6 address
62+
# or "127.0.0.1" or "::1" to bind to localhost IPv4 or IPv6, respectively
63+
# use the address of a specific network interface to just listen to and accept
64+
# connections from that interface
65+
#bind_to_address = 0.0.0.0
66+
67+
# which port the server should listen to
68+
#port = 1780
69+
70+
# serve a website from the doc_root location
71+
# disabled if commented or empty
72+
{{ if .http.doc_root }}
73+
doc_root = {{ .http.doc_root }}
74+
{{ end }}
75+
#
76+
###############################################################################
77+
78+
79+
# TCP RPC #####################################################################
80+
#
81+
[tcp]
82+
# enable TCP Json RPC
83+
{{ if .tcp.enabled }}
84+
enabled = {{ .tcp.enabled }}
85+
{{ end }}
86+
87+
# address to listen on, can be specified multiple times
88+
# use "0.0.0.0" to bind to any IPv4 address or :: to bind to any IPv6 address
89+
# or "127.0.0.1" or "::1" to bind to localhost IPv4 or IPv6, respectively
90+
# use the address of a specific network interface to just listen to and accept
91+
# connections from that interface
92+
#bind_to_address = 0.0.0.0
93+
94+
# which port the server should listen to
95+
#port = 1705
96+
#
97+
###############################################################################
98+
99+
100+
# Stream settings #############################################################
101+
#
102+
[stream]
103+
# address to listen on, can be specified multiple times
104+
# use "0.0.0.0" to bind to any IPv4 address or :: to bind to any IPv6 address
105+
# or "127.0.0.1" or "::1" to bind to localhost IPv4 or IPv6, respectively
106+
# use the address of a specific network interface to just listen to and accept
107+
# connections from that interface
108+
#bind_to_address = 0.0.0.0
109+
110+
# which port the server should listen to
111+
#port = 1704
112+
113+
# source URI of the PCM input stream, can be configured multiple times
114+
# The following notation is used in this paragraph:
115+
# <angle brackets>: the whole expression must be replaced with your specific setting
116+
# [square brackets]: the whole expression is optional and can be left out
117+
# [key=value]: if you leave this option out, "value" will be the default for "key"
118+
#
119+
# Format: TYPE://host/path?name=<name>[&codec=<codec>][&sampleformat=<sampleformat>][&chunk_ms=<chunk ms>]
120+
# parameters have the form "key=value", they are concatenated with an "&" character
121+
# parameter "name" is mandatory for all sources, while codec, sampleformat and chunk_ms are optional
122+
# and will override the default codec, sampleformat or chunk_ms settings
123+
# Non blocking sources support the dryout_ms parameter: when no new data is read from the source, send silence to the clients
124+
# Available types are:
125+
# pipe: pipe:///<path/to/pipe>?name=<name>[&mode=create][&dryout_ms=2000], mode can be "create" or "read"
126+
# librespot: librespot:///<path/to/librespot>?name=<name>[&dryout_ms=2000][&username=<my username>&password=<my password>][&devicename=Snapcast][&bitrate=320][&wd_timeout=7800][&volume=100][&onevent=""][&nomalize=false][&autoplay=false][&params=<generic librepsot process arguments>]
127+
# note that you need to have the librespot binary on your machine
128+
# sampleformat will be set to "44100:16:2"
129+
# file: file:///<path/to/PCM/file>?name=<name>
130+
# process: process:///<path/to/process>?name=<name>[&dryout_ms=2000][&wd_timeout=0][&log_stderr=false][&params=<process arguments>]
131+
# airplay: airplay:///<path/to/airplay>?name=<name>[&dryout_ms=2000][&port=5000]
132+
# note that you need to have the airplay binary on your machine
133+
# sampleformat will be set to "44100:16:2"
134+
# tcp server: tcp://<listen IP, e.g. 127.0.0.1>:<port>?name=<name>[&mode=server]
135+
# tcp client: tcp://<server IP, e.g. 127.0.0.1>:<port>?name=<name>&mode=client
136+
# alsa: alsa://?name=<name>&device=<alsa device>[&send_silence=false][&idle_threshold=100][&silence_threshold_percent=0.0]
137+
# meta: meta:///<name of source#1>/<name of source#2>/.../<name of source#N>?name=<name>
138+
139+
{{ range $source := .stream.sources }}
140+
source = {{ $source }}
141+
{{ end }}
142+
143+
# Default sample format
144+
{{ if .stream.sampleformat }}
145+
sampleformat = {{ .stream.sampleformat }}
146+
{{ end }}
147+
148+
# Default transport codec
149+
# (flac|ogg|opus|pcm)[:options]
150+
# Type codec:? to get codec specific options
151+
{{ if .stream.codec }}
152+
codec = {{ .stream.codec }}
153+
{{ end }}
154+
155+
# Default source stream read chunk size [ms]
156+
#chunk_ms = 20
157+
158+
# Buffer [ms]
159+
{{ if .stream.buffer }}
160+
codec = {{ .stream.buffer }}
161+
{{ end }}
162+
163+
# Send audio to muted clients
164+
{{ if .stream.send_to_muted }}
165+
send_to_muted = {{ .stream.send_to_muted }}
166+
{{ end }}
167+
168+
169+
#
170+
###############################################################################
171+
172+
173+
# Logging options #############################################################
174+
#
175+
[logging]
176+
177+
# log sink [null,system,stdout,stderr,file:<filename>]
178+
# when left empty: if running as daemon "system" else "stdout"
179+
#sink =
180+
{{ if .logging.sink }}
181+
sink = {{ .logging.sink }}
182+
{{ end }}
183+
184+
# log filter <tag>:<level>[,<tag>:<level>]*
185+
# with tag = * or <log tag> and level = [trace,debug,info,notice,warning,error,fatal]
186+
#filter = *:info
187+
{{ if .logging.filter }}
188+
filter = {{ .logging.filter }}
189+
{{ else }}
190+
filter = *:info
191+
{{ end }}
192+
#
193+
###############################################################################

0 commit comments

Comments
 (0)