{"id":324,"date":"2024-07-12T20:29:55","date_gmt":"2024-07-12T20:29:55","guid":{"rendered":"https:\/\/bytewyse.org\/?page_id=324"},"modified":"2024-07-25T19:59:37","modified_gmt":"2024-07-25T19:59:37","slug":"music-server","status":"publish","type":"page","link":"https:\/\/bytewyse.org\/?page_id=324","title":{"rendered":"Music Server"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">12 July 2024 V1.0<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Introduction<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you are motivated by a desire to not to have to pay a music streaming service to curate your music collection and to not even own it as it&#8217;s licenced back to you, you are in good company and have arrived in the right place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I have a moderately sized music collection that I have converted to FLAC format and stored on my Network Accessible Storage (NAS) that I stream within my house.&nbsp; For listening in the car the FLAC files have been transcoded to mp3 format at a bit rate of 256 kbps and copied to a 64 Gb USB thumb drive.&nbsp; However, this is far from ideal.&nbsp; My car&#8217;s music system doesn&#8217;t seem to be able to handle more than 64 Gb of music files and it becomes tedious when deciding on what to delete when copying new music to the thumb drive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This started me thinking about streaming music directly to my phone or other compatible device. My search eventually led me to <a href=\"https:\/\/www.navidrome.org\/about\/\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Navidrome<\/mark><\/a><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\"> <\/mark>and it seemed almost too good to be true. <a href=\"https:\/\/www.navidrome.org\/docs\/overview\/\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Key features<\/mark><\/a> are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Very&nbsp;<strong>low resource usage<\/strong>. Runs well even on simple Raspberry Pi Zero and old hardware setups<\/li>\n\n\n\n<li>Handles very&nbsp;<strong>large music collections<\/strong><\/li>\n\n\n\n<li>Streams virtually&nbsp;<strong>any audio format<\/strong>&nbsp;available<\/li>\n\n\n\n<li>Reads and uses all your beautifully curated&nbsp;<strong>metadata<\/strong><\/li>\n\n\n\n<li>Great support for&nbsp;<strong>compilations<\/strong>&nbsp;(Various Artists albums) and&nbsp;<strong>box sets<\/strong>&nbsp;(multi-disc albums)<\/li>\n\n\n\n<li><strong>Multi-user<\/strong>, each user has their own play counts, playlists, favorites, etc..<\/li>\n\n\n\n<li><strong>Multi-platform<\/strong>, runs on macOS, Linux and Windows.&nbsp;<strong>Docker<\/strong>&nbsp;images are also provided<\/li>\n\n\n\n<li>Ready to use, official,&nbsp;<strong>Raspberry Pi<\/strong>&nbsp;binaries and Docker images available<\/li>\n\n\n\n<li>Automatically&nbsp;<strong>monitors your library<\/strong>&nbsp;for changes, importing new files and reloading new metadata<\/li>\n\n\n\n<li><strong>Themeable<\/strong>, modern and responsive&nbsp;<strong>Web interface<\/strong>&nbsp;based on&nbsp;<a href=\"https:\/\/material-ui.com\/\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Material UI<\/mark><\/a>&nbsp;and&nbsp;<a href=\"https:\/\/marmelab.com\/react-admin\/\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">React-Admin<\/mark><\/a><\/li>\n\n\n\n<li><strong>Compatible<\/strong>&nbsp;with all Subsonic\/Madsonic\/Airsonic clients. See below for a list of tested clients<\/li>\n\n\n\n<li><strong>Transcoding<\/strong>&nbsp;on the fly. Can be set per user\/player.&nbsp;<strong>Opus encoding is supported<\/strong><\/li>\n\n\n\n<li>Translated to&nbsp;<strong>17 languages<\/strong>&nbsp;(<a href=\"https:\/\/www.navidrome.org\/docs\/developers\/translations\/\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">and counting<\/mark><\/a>)<\/li>\n\n\n\n<li>Full support for&nbsp;<strong>playlists<\/strong>, with option to auto-import&nbsp;.m3u&nbsp;files and to keep them in sync<\/li>\n\n\n\n<li><strong>Smart<\/strong>\/dynamic playlists (similar to iTunes). More info&nbsp;<a href=\"https:\/\/github.com\/navidrome\/navidrome\/issues\/1417\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">here<\/mark><\/a><\/li>\n\n\n\n<li>Scrobbling to&nbsp;<strong>Last.fm<\/strong>,&nbsp;<strong>ListenBrainz<\/strong>&nbsp;and&nbsp;<strong>Maloja<\/strong>&nbsp;(via custom ListenBrainz URL)<\/li>\n\n\n\n<li><strong>Sharing<\/strong>&nbsp;public links to albums\/songs\/playlists<\/li>\n\n\n\n<li><strong>Reverse Proxy<\/strong>&nbsp;authentication<a href=\"https:\/\/www.navidrome.org\/docs\/usage\/security\/#reverse-proxy-authentication\">*<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.navidrome.org\/docs\/usage\/jukebox\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Jukebox mode<\/mark><\/a>&nbsp;allows playing music on an audio device attached to the server, and control from a client<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading has-medium-font-size\">Bill of Materials<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Hardware<\/h3>\n\n\n\n<figure class=\"wp-block-table is-style-regular\"><table><thead><tr><th>Hardware<\/th><th>Qty<\/th><th>Unit Price<\/th><th>Price<\/th><th>Source<\/th><th>Part No.<\/th><\/tr><\/thead><tbody><tr><td>Raspberry Pi4 (4 Gb)<\/td><td>1<\/td><td>\u00a355.00<\/td><td>\u00a355.00<\/td><td>The Pi Hut<\/td><td>SC0194<\/td><\/tr><tr><td>Pi4 Case<\/td><td>1<\/td><td>\u00a314.00<\/td><td>\u00a314.00<\/td><td>The Pi Hut<\/td><td>102476<\/td><\/tr><tr><td>Pi4 PSU<\/td><td>1<\/td><td>\u00a39.00<\/td><td>\u00a39.00<\/td><td>The Pi Hit<\/td><td>SC0443<\/td><\/tr><tr><td>microSD (32Gb)<\/td><td>1<\/td><td>\u00a38.00<\/td><td>\u00a38.00<\/td><td>The Pi Hut<\/td><td>SD-32GB<\/td><\/tr><tr><td>External SSD (240 Gb)<\/td><td>1<\/td><td>\u00a336.50<\/td><td>\u00a336.50<\/td><td>The Pi Hut<\/td><td>103410<\/td><\/tr><tr><td>SSD Cable<\/td><td>1<\/td><td>\u00a36.00<\/td><td>\u00a36.00<\/td><td>The Pi Hut<\/td><td>103222<\/td><\/tr><tr><td>Total<\/td><td>&nbsp;<\/td><td>&nbsp;<\/td><td>\u00a3128.50<\/td><td>&nbsp;<\/td><td>&nbsp;<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">Table 1 &#8211; Hardware<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Prices correct at time of writing in June 2024<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Software<\/h3>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\">\n<figure class=\"wp-block-table\"><table><thead><tr><th>Software<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td>Navidrome<\/td><td>Streaming app<\/td><\/tr><tr><td>Raspberry PiOS<\/td><td>Operating System<\/td><\/tr><tr><td>Apache 2.4<\/td><td>Configure for reverse proxy with https<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">Table 2 &#8211; Software<\/figcaption><\/figure>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Software Installation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you haven&#8217;t got the Raspberry Pi OS imager get it here:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/downloads.raspberrypi.org\/imager\/imager_latest.exe\">https:\/\/downloads.raspberrypi.org\/imager\/imager_latest.exe<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And follow the installation instructions<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Get the most recent version of Raspberry Pi OS here:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/downloads.raspberrypi.com\/raspios_lite_arm64\/images\/raspios_lite_arm64-2024-03-15\/2024-03-15-raspios-bookworm-arm64-lite.img.xz\">https:\/\/downloads.raspberrypi.com\/raspios_lite_arm64\/images\/raspios_lite_arm64-2024-03-15\/2024-03-15-raspios-bookworm-arm64-lite.img.xz<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Or choose from the download options in the image installation software.&nbsp; As this is going to be a headless server I recommend getting the &#8220;lite&#8221; version.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Customise OS Settings<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Within the imager application change the following before writing the OS image to the SD card:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">General<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<ul class=\"wp-block-list\">\n<li>Set hostname : &lt;Your_hostname&gt;<\/li>\n\n\n\n<li>Set username and password<\/li>\n\n\n\n<li>Configure wireless LAN<\/li>\n\n\n\n<li>Set locale<\/li>\n\n\n\n<li>Services<\/li>\n\n\n\n<li>Enable SSH<\/li>\n<\/ul>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Use password authentication for SSH (we&#8217;ll setup public-key auth later)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apply custom settings and let the image be written to the SD card. When done, remove SD card from computer and insert into the RPi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Power up the RPi.\u00a0 As the RPi has setup with default network settings you will need to scan your network to discover the IP address that has been assigned via DHCP.\u00a0 Once you have determined the IP address of the RPi:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Login via SSH and update packages<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt full-upgrade<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo reboot<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Next, if it doesn&#8217;t exist, we\u2019re going to create the filesystem on the external SSD.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When rebooted, log back in via SSH and locate the external hard drive<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo fdisk -l<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Look for a device in the list that has the expected file size, e.g.&nbsp;\/dev\/sda.&nbsp; Use this to create the file system on the disk using parted.<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<pre class=\"wp-block-code\"><code>Disk <strong>\/dev\/sda<\/strong>: 223.57 GiB, 240057409536 bytes, 468862128 sectors\nDisk model: ASM1153USB3.0TOS\nUnits: sectors of 1 * 512 = 512 bytes\nSector size (logical\/physical): 512 bytes \/ 512 bytes\nI\/O size (minimum\/optimal): 512 bytes \/ 33553920 bytes<\/code><\/pre>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Create partition using GNU parted<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p class=\"wp-block-paragraph\"><mark style=\"background-color:#7bdcb5\" class=\"has-inline-color has-black-color\">Note: parted while relatively easy to use does have some quirks.&nbsp; It took a few trial runs and an Internet search for the warning messages to get a partition with settings that I was happy with.<\/mark><\/p>\n<\/div><\/div>\n<\/div><\/div>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo parted \/dev\/sda<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This starts parted in interactive mode and after a bit of fiddling around with the options to eliminate the &#8220;mis-alignment&#8221;, &#8220;non-optimised&#8221; messages I arrived at this for the command line options:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">mkpart gpt ext4 0% 100%<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Make the filesystem<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkfs.ext4 \/dev\/sda1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create mount point<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir \/media\/music<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Mount the drive<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mount&nbsp; \/media\/music<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To maximise the free space run<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tune2fs -r 0 \/dev\/sda1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Auto mount on reboot<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo blkid<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/dev\/sda1: UUID=\"24d169ac-0059-4932-bb75-10eb9c214ef8\" BLOCK_SIZE=\"4096\" TYPE=\"ext4\" PARTLABEL=\"gpt\" PARTUUID=\"2dc7ab0b-5407-4ced-b1c0-09f17fc6271f\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Edit <code>\/etc\/fstab<\/code> and insert<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>UUID=24d169ac-0059-4932-bb75-10eb9c214ef8&nbsp;&nbsp;&nbsp; \/media\/music&nbsp;&nbsp; ext4&nbsp; defaults&nbsp; 0&nbsp; 0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And reboot<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo reboot<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And that concludes the initial build of the RPi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Install &amp; Configure Navidrome<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Instructions here:<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.navidrome.org\/docs\/installation\">https:\/\/www.navidrome.org\/docs\/installation<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Navidrome can be installed either through Docker or with pre-compiled binaries.&nbsp; Most of the tutorials for installing Navidrome seem to focus on using Docker.&nbsp; Given the range of pre-compiled binaries available I&#8217;m not sure why there is such a focus on installing with Docker<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To determine which package need to be downloaded type:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>uname -a<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Linux neutrino 6.6.28+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.6.28-1+rpt1 (2024-04-22) <strong>aarch64 GNU\/Linux<\/strong><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">aarch64 is the same as arm64 so the package required is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><a href=\"https:\/\/github.com\/navidrome\/navidrome\/releases\/download\/v0.52.5\/navidrome_0.52.5_linux_arm64.tar.gz\">https:\/\/github.com\/navidrome\/navidrome\/releases\/download\/v0.52.5\/navidrome_0.52.5_linux_arm64.tar.gz<\/a><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Download the binary as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/home\/pi\/Downloads<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code><code>wget <a href=\"https:\/\/github.com\/navidrome\/navidrome\/releases\/download\/v0.52.5\/navidrome_0.52.5_linux_arm64.tar.gz\">https:\/\/github.com\/navidrome\/navidrome\/releases\/download\/v0.52.5\/navidrome_0.52.5_linux_arm64.tar.gz<\/a> -O Navidrome.tar.gz<\/code><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install dependencies<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install vim ffmpeg<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create user: navidrome<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo adduser navidrome --system --no-create-home<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create group: navidrome<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd navidrome<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add user navidrome to group navidrome<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo usermod --append --groups navidrome navidrome<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create directory structure<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo install -d -o navidrome -g navidrome \/opt\/navidrome<br>sudo install -d -o navidrome -g navidrome \/var\/lib\/navidrome<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Install navidrome<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<pre class=\"wp-block-code\"><code>sudo tar -xvzf Navidrome.tar.gz -C \/opt\/navidrome\/\nsudo chown -R navidrome:navidrome \/opt\/navidrome<\/code><\/pre>\n<\/div><\/div>\n<\/div><\/div>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Create configuration file<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/var\/lib\/navidrome<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In the working directory,&nbsp;<code>\/var\/lib\/navidrome<\/code>&nbsp;create a new file named&nbsp;<code>navidrome.toml&nbsp;<\/code>with the following settings:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>sudo nano navidrome.toml <\/code>and add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MusicFolder <strong>= <\/strong>\"\/media\/music\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a Systemd unit called <code>navidrome.service<\/code> in <code>\/etc\/systemd\/system<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/etc\/systemd\/system\nsudo touch navidrome.service\nsudo chmod 644 navidrome.service\nsudo vi navidrome.service<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add the following to the unit:<\/p>\n\n\n\n<pre class=\"wp-block-code has-medium-font-size\"><code><strong>&#91;Unit]<\/strong>\nDescription=Navidrome Music Server and Streamer compatible with Subsonic\/Airsonic\nAfter=remote-fs.target network.target\nAssertPathExists=\/var\/lib\/navidrome\n<strong>&#91;Install]<\/strong>\nWantedBy=multi-user.target\n<strong>&#91;Service]<\/strong>\nUser=&lt;user&gt;\nGroup=&lt;group&gt;\nType=simple\nExecStart=\/opt\/navidrome\/navidrome --configfile \"\/var\/lib\/navidrome\/navidrome.toml\"\nWorkingDirectory=\/var\/lib\/navidrome\nTimeoutStopSec=20\nKillMode=process\nRestart=on-failure\n<em># See <\/em><a href=\"https:\/\/www.freedesktop.org\/software\/systemd\/man\/systemd.exec.html\"><em>https:\/\/www.freedesktop.org\/software\/systemd\/man\/systemd.exec.html<\/em><\/a>\nDevicePolicy=closed\nNoNewPrivileges=yes\nPrivateTmp=yes\nPrivateUsers=yes\nProtectControlGroups=yes\nProtectKernelModules=yes\nProtectKernelTunables=yes\nRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nRestrictNamespaces=yes\nRestrictRealtime=yes\nSystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap\nReadWritePaths=\/var\/lib\/navidrome\n<em># You can uncomment the following line if you're not using the jukebox This<\/em>\n<em># will prevent navidrome from accessing any real (physical) devices<\/em>\n<em>#PrivateDevices=yes<\/em>\n<em># You can change the following line to `strict` instead of `full` if you don't<\/em>\n<em># want navidrome to be able to write anything on your filesystem outside of<\/em>\n<em># \/var\/lib\/navidrome.<\/em>\nProtectSystem=full\n<em># You can uncomment the following line if you don't have any media in \/home\/*.<\/em>\n<em># This will prevent navidrome from ever reading\/writing anything there.<\/em>\n<em>ProtectHome=true<\/em>\n<em># You can customize some Navidrome config options by setting environment variables here. Ex:<\/em>\n<em>#Environment=ND_BASEURL=\"\/navidrome\"<\/em><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now start Navidrome and check that it is running correctly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl daemon-reload\nsudo systemctl start navidrome.service\nsudo systemctl status navidrome.service<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enable startup on boot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl enable navidrome.service<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s pretty much the initial configuration complete.&nbsp; If you run a zoned network you might need to add or adjust your firewall rules at this point to permit access on the internal network. At this point your Navidrome server isn&#8217;t (shouldn&#8217;t be) visible externally on the Internet. That requires some additional configuration that is discussed in later sections.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If Navidrome is being run on a separate network or sub net jump forward at this point to Firewall Configuration to set the rules required and then return here when done.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Connect to Navidrome for the first time by browsing to its home page at:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><a href=\"http:\/\/%3cIIP\">http:\/\/&lt;IIP<\/a> Address for Navidrome&gt;:4533<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create your first user. This will be your admin user, a super user that can manage all aspects of Navidrome, including the ability to manage other users.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Post Install Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Set hostname by editing \/etc\/hostname and adding the FQDN for your server<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;hostname.domain.tld&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Although Navidrome is now installed properly and can be used, it is highly recommended that you implement the following post-installation steps.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><a href=\"https:\/\/www.navidrome.org\/docs\/usage\/security\/#encrypted-passwords\"><strong><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Encrypted passwords<\/mark><\/strong><\/a><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><em>&#8220;To be able to keep compatibility with the Subsonic API and its clients, Navidrome needs to store user\u2019s passwords in its database. By default, Navidrome encrypts the passwords in the DB with a shared encryption key, just for the sake of obfuscation as this key can be easily found in the codebase.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>This key can be overridden by the config option PasswordEncryptionKey. Once this option is set and Navidrome is restarted, it will re-encrypt all passwords with this new key. <\/em><strong><em>This is a one-time only configuration, and after this point the config option cannot be changed anymore or else users won\u2019t be able to authenticate.<\/em><\/strong><em>&#8220;<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Set Encryption Key<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Due to limitations with the Subsonic API, Navidrome is unable to properly hash passwords and thus encrypts them.&nbsp; We need to change the default encryption key.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Edit the navidrome.toml file in \/var\/lib\/navidrome again and set the PasswordEncryptionKey option.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PasswordEncryptionKey = \"&lt;Your Strong Encryption Key Here\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Along with adding a password encryption key some additional tweaks to the navidrome configuration file can be made with the following lines:<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<pre class=\"wp-block-code\"><code>EnableTranscodingConfig = false\nLogLevel = 'DEBUG'\nScanSchedule = '@every 12h'\nTranscodingCacheSize = '1000MiB'\nAddress = \"&lt;IP Address of Server&gt;\"\nBaseUrl = \"\/\"<\/code><\/pre>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Don&#8217;t forget to restart Navidrome again to reload the config file<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl restart navidrome<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Copy Music Files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Copy music files from local storage to Navidrome (assumes that you are in the root directory for your music) by logging in on SSH to the local storage. Once logged in navigate to the root directory of you music files and type:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>scp -rv * pi@&lt;IP Address&gt;:\/media\/music<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Enter password for remote server when requested.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Depending on the size of your music collection this might take a while.&nbsp; Go and make yourself a drink\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your local storage is a Synology NAS and you\u2019ve installed the MediaServer or PhotoStation packages you will find that these packages run a service that creates hidden directories named &#8220;@eaDir&#8221;.&nbsp; These directories are the equivalent of thumbs.db on Windows where the package stores thumbnail files associated with iTunes support and will be copied across.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you don&#8217;t have or need iTunes support the @eaDir directories can be safely deleted with the command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>find<\/strong> . -type d -name \"@eaDir\" -print0 <strong>| <\/strong><strong>xargs <\/strong>-0 <strong>rm <\/strong>-rf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To keep in sync either setup rsync with a cron job or if you are lazy, like me, and run Windows use WinSCP to copy across any new music that you get.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">External Access Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Since the whole purpose of setting up a music server is to make it accessible from the Internet there are a few more steps that need to be taken to ensure that Navidrome can be accessed externally and securely.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In no particular order these are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Configure your ISP&#8217;s or hosting provider&#8217;s DNS server for your domain to &#8220;point&#8221; at the Navidrome server public IP address;<\/li>\n\n\n\n<li>Configure the necessary firewall rules and\/or port forwarding rules to ensure that the server is reachable from its public IP address to whatever the internal IP address of the server is;<\/li>\n\n\n\n<li>Configure a reverse proxy server to allow secure access via HTTPS;<\/li>\n\n\n\n<li>Get or update your existing TLS Certificate.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">As I already host a number of services making the necessary changes was fairly straightforward.&nbsp; The DNS change involved nothing more than adding a CNAME record to point at my existing A record. The DNS entry is below:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Host\/name: &lt;Your_hostname&gt;\nType: CNAME\nTTL: 1 hour\nValue: &lt;Your_domain_name.&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The firewall rules and forwarding were already in place and I run Apache 2.4 with Virtual Hosts, each virtual host being managed through SNI.&nbsp; As the Navidrome server is being run on a separate host on the main domain I needed to increase the scope of the TLS certificate from LetsEncrypt as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo certbot --expand -d foo.example.com -d bar.example.com -d baz.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And follow the terminal prompts to generate and install the expanded certificate.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Firewall Configuration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Policies (rules) required<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create custom policy (rule) for Navidrome<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Protocol<\/strong><\/td><td><strong>Source Ports<\/strong><\/td><td><strong>Destination Port<\/strong><\/td><\/tr><tr><td>TCP<\/td><td>0 &#8211; 65535<\/td><td>4533<\/td><\/tr><tr><td>UDP<\/td><td>0 &#8211; 65535<\/td><td>4533<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Trust &gt;&gt; DMZ2<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Service<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><tr><td>SSH<\/td><td>Permit SSH or SCP to allow copying of current and new music to Navidrome<\/td><\/tr><tr><td>Navidrome<\/td><td>Allow access from local network to Navidrome on port 4533 (HTTP)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Parameter<\/strong><\/td><td><strong>Network\/host<\/strong><\/td><td><strong>Comment<\/strong><\/td><\/tr><tr><td>Source IP:<\/td><td>192.168.x.x\/24<\/td><td>Trusted network<\/td><\/tr><tr><td>Destination IP:<\/td><td>192.168.x.x\/32<\/td><td>Navidrome host<\/td><\/tr><tr><td>Routing:<\/td><td>NAT none<\/td><td>NAT not required<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">DMZ &gt;&gt; DMZ2<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Service<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><tr><td>Navidrome<\/td><td>Allow access from local network to Navidrome on port 4533 (HTTP) via reverse proxy<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Parameter<\/strong><\/td><td><strong>Network\/host<\/strong><\/td><td><strong>Comment<\/strong><\/td><\/tr><tr><td>Source IP<\/td><td>192.168.x.x\/24<\/td><td>DMZ network<\/td><\/tr><tr><td>Destination IP<\/td><td>192.168.x.x\/32<\/td><td>Navidrome host<\/td><\/tr><tr><td>Routing<\/td><td>NAT none<\/td><td>NAT not required<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">DMZ2 &gt;&gt; Untrust<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Permit access to external hosts<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Service<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><tr><td>DNS<\/td><td>Name Resolution<\/td><\/tr><tr><td>HTTP<\/td><td>Update Servers<\/td><\/tr><tr><td>HTTPS<\/td><td>Update Servers<\/td><\/tr><tr><td>FTP<\/td><td>Update Servers<\/td><\/tr><tr><td>NTP<\/td><td>Time Service<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Parameter<\/strong><\/td><td><strong>Network\/host<\/strong><\/td><td><strong>Comment<\/strong><\/td><\/tr><tr><td>Source IP<\/td><td>192.168.x.x\/32<\/td><td>Navidrome host<\/td><\/tr><tr><td>Destination IP<\/td><td>Any IPv4<\/td><td>Connect to all IPv4 addresses<\/td><\/tr><tr><td>Routing: NAT<\/td><td>(Source Translation)<\/td><td>NAT required<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">DMZ2 &gt;&gt; DMZ<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Permit traffic to pass back through reverse proxy<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Service<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><tr><td>Navidrome<\/td><td>Allow access from local network to Navidrome on port 4533 (HTTP) via reverse proxy<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Parameter<\/strong><\/td><td><strong>Network\/host<\/strong><\/td><td><strong>Comment<\/strong><\/td><\/tr><tr><td>Source IP<\/td><td>192.168.x.x\/32<\/td><td>Navidrome host<\/td><\/tr><tr><td>Destination IP<\/td><td>192.168.x.x\/24<\/td><td>DMZ network<\/td><\/tr><tr><td>Routing<\/td><td>NAT none<\/td><td>NAT not required<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Port Forwarding<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>Source IP<\/strong><\/td><td><strong>Destination IP<\/strong><\/td><td><strong>Port<\/strong><\/td><\/tr><tr><td>Public IP address<\/td><td>192.168.x.x<\/td><td>80<\/td><\/tr><tr><td>Public IP<\/td><td>192.168.x.x<\/td><td>443<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Note: Port 80 should be permanently redirected to port 443 for HTTPS on the reverse proxy<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reverse Proxy Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The notes below are linked and taken directly from the Navidrome website.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><a href=\"https:\/\/www.navidrome.org\/docs\/usage\/security\/#network-configuration\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Network configuration<\/mark><\/a><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><em>&#8220;Even though Navidrome comes with an embedded, full-featured HTTP server, you should seriously consider running it behind a reverse proxy (Ex: Caddy, Nginx, Traefik, Apache) for added security, including setting up SSL. There are tons of good resources on the web on how to properly setup a reverse proxy.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>When using Navidrome in such configuration, you may want to prevent Navidrome from listening to all IPs configured in your computer, and only listen to localhost. This can be achieved by setting the Address flag to localhost&#8221;<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><a href=\"https:\/\/www.navidrome.org\/docs\/usage\/security\/#reverse-proxy-authentication\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">Reverse proxy authentication<\/mark><\/a><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><em>&#8220;When reverse proxy authentication is used, the verification is done by another system. By checking a specific HTTP header, Navidrome assumes you are already authenticated. This header can be configured via <\/em><em>ReverseProxyUserHeader<\/em><em> configuration option. By default, the <\/em><em>Remote-User<\/em><em> header is used.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>By default, Navidrome denies every attempt. Authentication proxy needs to be whitelisted in CIDR format, using ReverseProxyWhitelist. Both IPv4 and IPv6 are supported.&#8221;<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Edit the navidrome.toml file and add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ReverseProxyUserHeader = \"Remote-User\"\nReverseProxyWhitelist = \"&lt;IP Address_of_proxy_here&gt;\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Edit the <code>httpd.conf<\/code> file in <code>\/etc\/httpd\/conf<\/code> and add the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost *:80&gt;\n     ServerName &lt;Your_FQDN_here&gt;\n     Redirect permanent \/ https:\/\/&lt;Your_FQDN_here&gt;\n&lt;\/VirtualHost&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Edit the <code>httpd-le-ssl.conf<\/code> file and add the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;VirtualHost *:443&gt;\n     ServerName &lt;Your_FQDN_here&gt;\n     ProxyPass \/ http:\/\/192.168.x.x:4533\/\n     ProxyPassReverse \/ http:\/\/192.168.x.x:4533\/\n     DocumentRoot \"\/var\/www\/&lt;domain_name&gt;\/&lt;host_name&gt;\/html\/\"\n     DirectoryIndex index.htm<\/code><\/pre>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<pre class=\"wp-block-code\"><code>&lt;Directory \/var\/www\/&lt;domain_name&gt;\/&lt;host_name&gt;\n     Options +Indexes +FollowSymLinks +MultiViews\n     AllowOverride All\n     Require all granted\n&lt;\/Directory&gt;<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># Other directives here<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Header always set Strict-Transport-Security \"max-age=63072000; includeSubdomains;\"\nInclude \/etc\/letsencrypt\/options-ssl-apache.conf\nSSLCertificateFile \/etc\/letsencrypt\/live\/&lt;domain.tld&gt;\/cert.pem\nSSLCertificateKeyFile \/etc\/letsencrypt\/live\/&lt;domain.tld&gt;\/privkey.pem\nSSLCertificateChainFile \/etc\/letsencrypt\/live\/&lt;domain.tld&gt;\/chain.pem\n&lt;\/VirtualHost&gt;<\/code><\/pre>\n<\/div><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Restart httpd server:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl restart httpd<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s it. You should now be able to connect to Navidrome at <a href=\"https:\/\/%3cFQDN%3e\">https:\/\/&lt;FQDN&gt;<\/a> via your web browser and if all is working you will be presented with the login page.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final Thoughts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Navidrome is an awesome piece of software. In the few minutes between starting it up and creating the admin user it had already indexed the first 300 albums that I had copied to the server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It\u2019s quick and intuitive to use from the built in web server.&nbsp; I have also tried connecting using SubStreamer, Amperfy and HalpoPlayer. All work and of the three HalpoPlayer is a clear favourite for me with its clear, minimalist, no nonsense interface. SubStreamer would have been my preferred player as it\u2019s much richer in its features. However, while appearing to have been around much longer it has not had an update for over a year and is riddled with bugs and stability issues that made it unusable for me.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The single biggest problem for me was configuring the reverse proxy. The learning experience here is: pay attention to the syntax and don\u2019t omit trailing \u201c\/\u201c on the server URL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, I think that the Navidrome documentation on reverse proxy configuration is a bit \u201csketchy\u201d around authentication and IP whitelisting. There are essentially two configuration settings:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>&#8220;When reverse proxy authentication is used, the verification is done by another system. By checking a specific HTTP header, Navidrome assumes you are already authenticated. This header can be configured via <\/em><em>ReverseProxyUserHeader<\/em><em> configuration option. By default, the <\/em><em>Remote-User<\/em><em> header is used.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>By default, Navidrome denies every attempt. Authentication proxy needs to be whitelisted in CIDR format, using ReverseProxyWhitelist. Both IPv4 and IPv6 are supported.&#8221;<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In respect of \u201cReverseProxyUserHeader\u201d it\u2019s not clear what the purpose of setting it to \u201cRemote-User\u201d is or whether it can take on any other values. It&#8217;s also not clear what specific HTTP header is being referred to here.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>12 July 2024 V1.0 Introduction If you are motivated by a desire to not to have to pay a music streaming service to curate your music collection and to not even own it as it&#8217;s licenced back to you, you are in good company and have arrived in the right place. I have a moderately [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":36,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-324","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/pages\/324","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bytewyse.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=324"}],"version-history":[{"count":30,"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/pages\/324\/revisions"}],"predecessor-version":[{"id":375,"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/pages\/324\/revisions\/375"}],"up":[{"embeddable":true,"href":"https:\/\/bytewyse.org\/index.php?rest_route=\/wp\/v2\/pages\/36"}],"wp:attachment":[{"href":"https:\/\/bytewyse.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}