build-owntone/README.md

357 lines
14 KiB
Markdown

# build-owntone
[![Build Status](https://jenkins.sudo.is/buildStatus/icon?job=ben%2Fbuild-owntone%2Fmain&style=flat-square)](https://jenkins.sudo.is/job/ben/job/build-owntone/job/main/)
[![version](https://jenkins.sudo.is/buildStatus/icon?job=ben%2Fbuild-owntone%2Fmain&style=flat-square&status=${description}&subject=version&build=lastStable&color=blue)](https://git.sudo.is/ben/build-owntone/packages)
[![git](https://www.sudo.is/readmes/git.sudo.is-ben.svg)](https://git.sudo.is/ben/build-owntone)
[![github](https://www.sudo.is/readmes/github-benediktkr.svg)](https://github.com/benediktkr/build-owntone)
[![matrix](https://www.sudo.is/readmes/matrix-ben-sudo.is.svg)](https://matrix.to/#/@ben:sudo.is)
A custom build of the latest stable version of [OwnTone](https://github.com/owntone/owntone-server) (formerly forked-daapd).
[![owntone logo](img/owntone.png)](https://github.com/owntone)
This projects builds, compiles and packages OwnTone, publishing both `.deb` packages
and docker containers of the latest stable version.
# Changes to OwnTone
This build includes some (minor) custom changes to Owntone
* Attempting to use a dark theme (css was generated by the Dark Reader
extension for Firefox).
* Builds the web interface from `web-src` sources.
* Defaults to building the web interface, but can be disabled.
* Builds also contain the pre-compiled web interface in `htdocs`
* Builds web interface without minimized javascript
* Web socket is access by the web interface on `/ws` (requires reverse proxy) instead of
separate port (see [`web-change-ws-url.sh`](.pipeline/web-change-ws-url.sh))
* Example nginx vhost config file is included
(see [`01-owntone.conf`](etc/nginx/sites-available/01-owntone.conf)).
TODO changes:
* Change the default action to add an item to the queue instead of replacing the queue (see notes below). On
a phone or touchscreen it is not so easy to tap the hamburger menu and I often accidentally wipe out my
current queue.
## Not a fork
I don't want to have to maintain a whole fork of OwnTone, and these are all
relatively small and simple, so they are just applied on build-time with shell
scripts.
# Installing and usage
Config and example files:
* [`owntone.conf`](etc/owntone.conf): OwnTone config file with sane defaults
for this build.
* [`01-owntone.conf`](etc/nginx/sites-available/01-owntone.conf): Nginx vhost.
* [`owntone-docker.yml`](ansible/owntone-docker.yml): Ansible example tasks for
running the docker container.
## Installing `.deb` with `apt`
You can either user [the gitea repo from `git.sudo.is`](https://git.sudo.is/ben/-/packages/debian/owntone-server/) or [`apt.sudo.is`](https://apt.sudo.is) repo.
For `git.sudo.is`:
```shell
sudo curl https://git.sudo.is/api/packages/ben/debian/repository.key -o /etc/apt/keyrings/git.sudo.is-ben.asc
echo "deb [signed-by=/etc/apt/keyrings/git.sudo.is-ben.asc] https://git.sudo.is/api/packages/ben/debian $distribution main" | sudo tee -a /etc/apt/sources.list.d/git.sudo.is-ben.list
sudo apt update
sudo apt install owntone-server
```
For `apt.sudo.is`:
```shell
sudo curl https://apt.sudo.is/KEY.gpg -o /etc/apt/keyrings/apt.sudo.is.gpg
echo "deb [signed-by=/etc/apt/keyrings/apt.sudo.is.gpg] https://apt.sudo.is /" | sudo tee -a /etc/apt/sources.list.d/apt.sudo.is.list
sudo apt update
sudo apt install owntone-server
```
Generally the `git.sudo.is` mirror should probably be preferred.
## Docker container
For Ansible users, an example `docker_container` task is in [`owntone-docker.yml`](ansible/owntone-docker.yml).
The docker images are based on the `ubuntu:latest` base image, because of the
complex dependencies, and I was not able to figure out how to compile
a static binary. I tried following the github pipeline in [`owntone/owntone-apt`](https://github.com/owntone/owntone-apt)
but opted for using `fpm` to build a simple `.deb` package. Owntone is compiled and
packaged in the `builder` stage, and then installed in the `final` stage.
To run as a non-root user, you need to use `avahi-daemon` and `dbus` from the
host.
* https://github.com/mviereck/x11docker/issues/271
* https://github.com/mviereck/x11docker/wiki/How-to-connect-container-to-DBus-from-host
To do that you can either run the container as `--privileged` (wrong), or
set `--security-opt apparmor=unconfined` to circumvent AppArmor (better). In both
cases, the process acessing `/var/run/avahi-daemon/socket` needs to have the
same UID as owns the socket.
```shell
docker run \
-it \
--name owntone \
--net=host \
--security-opt apparmor=unconfined \
--user ${uid}:${gid} \
-v /var/run/dbus:/var/run/dbus \
-v /run/avahi-daemon/socket:/run/avahi-daemon/socket \
-v /usr/local/etc/owntone.conf:/etc/owntone.conf \
-v /var/lg/owntone.log:/var/log/owntone.log \
-v /var/cache/owntone/:/var/cache/owntone \
-v /srv/audio/:/srv/audio \
git.sudo.is/ben/owntone-server:latest
```
The container has the build args `OWNTONE_UID` and `OWNTONE_GID` if you
want to build it with a different default user for running OwnTone. Note
that you can change the defaults paths `/srv/audio` and
`/var/cache/owntone` in `owntone.conf` to better suit your needs, if
you want.
The container can also be started as root, and then the `owntone` binary `setuid`s
itself down to the user that you have specified in
`owntone.conf`. This is the default and expected behaviour of OwnTone.
```shell
docker run \
-it \
--name owntone \
--net=host \
--user 0 \
-v /usr/local/etc/owntone.conf:/etc/owntone.conf \
-v /var/log/owntone.log:/var/log/owntone.log \
-v /var/cache/owntone/:/var/cache/owntone \
-v /srv/audio/:/srv/audio \
git.sudo.is/ben/owntone-server:latest
```
When the container is started as root, neither the dbus nor avahi-daemon sockets
need to be mounted, because the container will start its own before launching OwnTone.
Note that this username (cant specify uid) has to
exist in the container (so if you want to change it you can rebuild the
container with different `UID` and `GID` build args, use some other UID that
already exists in the containers `/etc/passwd` or create a new
user in a custom `entrypoint.sh` script.)
# Differences from upstream OwnTone
## Websocket accessed on `/ws` via reverse proxy
By default OwnTone will spawn a websocket server on port thats separate from the main
port (used for the API and webinterface), because it is not expecting to be run behind
a reverse proxy. But if you do have a reverse proxy in front of OwnTone (like me), its
generally nicer to not have to deal with nonstandard ports and manage extra certificates
for them.
Since this isnt a fork, and we're keeping the changeset to a minimum, how OwnTone's
websocket server listens hasn't actually been changed. Instead, we've just changed
what address the web interface uses to connect to the websocket, and then we let the
reverse proxy take care of proxying that to OwnTone's websocket server.
This is the change to the web interface:
```diff
diff --git a/web-src/src/App.vue b/web-src/src/App.vue
index 0636fa10..1f6b0bc7 100644
--- a/web-src/src/App.vue
+++ b/web-src/src/App.vue
@@ -168,6 +168,7 @@ export default {
vm.$store.state.config.websocket_port
}
+ wsUrl = protocol + window.location.hostname + '/ws'
const socket = new ReconnectingWebSocket(wsUrl, 'notify', {
reconnectInterval: 1000,
maxReconnectInterval: 2000
```
We just re-define the `wsUrl` variable, right before it gets used (for the one and only
time). Simple.
The settings `websocket_interface` and `websocket_port` port are still used by OwnTone,
amd so are `port` and `bind_address` for the API. In fact, if you `GET /api/config`
the response still uses the `websocket_port` value:
```console
local:~$ curl -i https://${owntone_url}/api/config | jq .
{
"websocket_port": 3688,
// ...
}
```
Normally, the web interface reads this and uses the `websocket_port` in the response for
to construct the URL that it tries to connect to the websocket server on. It still reads
this config, it just doesn't end up using `websocket_port` because we have re-defined it.
Since the config options are still used by OwnTone, we just need to configure OwnTone
to bind both the API server and websocket server to `localhost` in `owntone.conf`. This
build [inclues a default `owntone.conf` file](etc/owntone.conf) that uses the default
ports but binds both servers to `localhost`:
```nginx
general {
# TCP port to listen on. Default port is 3689 (daap)
port = 3689
bind_address = "127.0.0.1" # default: "0.0.0.0"
websocket_port = 3688
websocket_interface = "127.0.0.1" # default: "0.0.0.0"
}
```
Since both are now only listening on `localhost`, we need a reverse proxy to proxy
the traffic to OwnTone. These builds include a config for Nginx, but you can of
course use any old HTTP(S) reverse proxy. Note that due to some idiosyncrasies
of the DAAP protocol, most clients expect to talk to OwnTone in HTTP/1.1. Some clients
work fine with HTTP/2 (for example Apple Remote) but others won't work and give you
somewhat confusing errors about mismatching versions (implying that it is about the
version of OwnTone or the DAAP protocol).
An example vhost config file [`01-owntone.conf`](etc/nginx/sites-available/01-owntone.conf)
for Nginx is provided.
If you use Nginx, it is a little bit peculiar about how HTTP versions in the `listen`
directive are set. All virtual hosts with the same `address:port` par will use the same
http version, though the [nginx documentation for the `ngx_http_core_module`](https://nginx.org/en/docs/http/ngx_http_core_module.html#listen)
makes no mention of this:
> The `http2` parameter (1.9.5) configures the port to accept HTTP/2 connections.
> Normally, for this to work the ssl parameter should be specified as well,
> but nginx can also be configured to accept HTTP/2 connections without SSL.
It's not possible to configure different virtual hosts (on the same `address:port`) to
serve different HTTP versions, Nginx _will always use the HTTP version set in the first
virtual host that it finds, and ignore the setting for all other virtual hosts_. If you
try to specify a "narrower" `address:port` pair than other vhsots, then your OwnTone
vhost will become the default for that pair, and start responding for other vhosts as
well. If this is a limitation that you have to work around (and you want to keep using
nginx) then the best option is to assign a second IP address to the host, and not bind
any other nginx vhosts (that use HTTP/2) to it.
## Partial filescans from [`owntone-server#1179`](https://github.com/owntone/owntone-server/pull/1179)
There is an open PR [`owntone-server#1179`](https://github.com/owntone/owntone-server/pull/1179)
from [:github: whatdoineed2do](https://github.com/whatdoineed2dothat) that
adds support for partial library file scans. This branch/fork also includes some
rather neat UI improvements to the web interface.
This featue is very useful if your library is on a network mount (no `inotify`) and
you for example liksten to mostly podcasts and want your library/queue stay up to date.
This build doesnt include this change by default, but you can enable it if you
build it yourself.
```shell
scan_path=/media/audio/podcasts
curl -X PUT "https://${owntone_url}/api/update?kind=files&path=${scan_path}"
```
Will start a partial library recan, scanning only `$scan_path` without rescanning
the rest of the library.
# Work in progress and future changes
## Dark Reader theme
This is a work in progress to build the web interface with the dark theme generated by
the [Dark Reader](https://github.com/darkreader/darkreader) Firefox extension.
As I lack moden HTML/CSS skills and havent gotten it to work yet (help welcome).
![](img/dark-reader.png)
These are the relevant files:
* [`dark-reader.css`](dark-reader/dark-reader.css): CSS exported from the Dark Reader Firefox extension
* [`index.html`](dark-reader/index.html): A copy of `index.html` the includes `<link>` tag
to include `dark-reader.css`
* [`web-add-dark-reader.sh`](.pipeline/web-add-dark-reader.sh): Script that gets invoked
during the build if `OWNTONE_WEB_DARK_READWER` is `"true"`.
Some references:
* https://stackoverflow.com/questions/62797830/using-dark-reader-extension-code-for-your-own-website
* https://github.com/darkreader/darkreader/issues/11638
## UI improvements
When clicking an item in the UI, the default is replace the current queue with whatever it
was that you clicked.
The is slightly annoying, especially when using a phone or touch-interface to navigate
OwnTone. I find that trying to hit the three-dots hamburger menu often results in accidentally
selecting the item, and replacing the queue.
A better default would be to "Add to queue" or evben "Add next", instead of replacing
the whole queue.
I think this happens in or around [`/src/webapi/index.js#L92`](https://github.com/owntone/owntone-server/blob/master/web-src/src/webapi/index.js#L78-L112),
or at least that should provide enough clues to find it in the code.
When an item is clicked in the web interface, a `POST` request is sent, for example: `/api/queue/items/add?uris=library:track:40397&shuffle=false&clear=true&playback=start`.
Breaking the parameters down into more-readable JSON:
```json
{
"uris": "library:track:1234",
"shuffle": false,
"clear": true,
"playback": start
}
```
Basically, we would want to send a request with `?clear=false` instead.
## Maintaining more complex changes without forking
To manage more complex changes without needing a fork, you can use `git diff`s
as `.patch` files.
Create the patch file:
```shell
cd owntone-server
git diff --no-prefix --patch > ../${name}.patch
```
Then apply the patch file:
```shell
patch -p1 -t -i ../${name}.patch
# or by using git:
git apply ../${name}.patch
```
## mdns
To forward mdns packets from the host network to the contaner:
```shell
mdns-repeater eth1 docker0
```
# Repositories
* :gitea: [`git.sudo.is/ben/build-owntone`](https://git.sudo.is/ben/build-owntone)
* :github: [`benediktkr/build-owntone`](https://github.com/benediktkr/build-owntone) (mirror, PRs are accepted)
* :package: [`build-owntone/packages/`](https://git.sudo.is/ben/build-owntone/packages)