Scripts to set up a headless Debian/Ubuntu box as a Music Player Daemon (MPD) audio server, with myMPD (web UI), mpDris2 (MPRIS2/D-Bus bridge), MergerFS pooled storage, Docker, and automatic security updates.
Run everything interactively via menu.sh, or run individual scripts
directly. Most scripts must be run as root (sudo ./script.sh); each checks
for this itself and exits with an error if not.
build-mpd.sh, setup-mergerfs.sh, generate-mpd-conf.sh, and
setup-log-rotation.sh read shared settings (MPD version, MergerFS
source/target directories, and mpd.conf paths/ports) from
mpd-audio.conf instead of hardcoded values. Copy the template and edit
it before running any of them:
cp mpd-audio.conf.example mpd-audio.conf
$EDITOR mpd-audio.confmpd-audio.conf is gitignored, so local edits won't conflict with a
future update to the scripts. MERGERFS_SOURCE_DIRS/MERGERFS_TARGET_DIR
must be changed from their placeholder values; everything else has a
working default.
install-docker.sh's HOMELAB_ROOT (used by the mkdc helper it sets
up) works differently: you don't need to copy anything in advance - the
script prompts for it on first run and writes install-docker.conf
itself, so later runs don't ask again. Copy
install-docker.conf.example yourself only if you want to skip the
prompt entirely (e.g. for unattended provisioning).
chmod +x *.sh
./menu.shmenu.sh presents a numbered list and runs the corresponding script with
sudo where needed. It does not exit on a sub-script failure — it returns
you to the menu so you can retry or move on.
Order matters for steps 6, 7, and 12 (
setup-bluetooth-audio.sh,generate-mpd-conf.sh,setup-alsa-equalizer.sh). Pair Bluetooth devices before generatingmpd.conf—generate-mpd-conf.shonly offers Bluetooth devices that are already paired. Wrap outputs with the ALSA EQ after generatingmpd.conf—setup-alsa-equalizer.shreads/etc/mpd.confto find what to wrap. Once/etc/mpd.confexists, bothsetup-bluetooth-audio.shandsetup-alsa-equalizer.shedit it directly; if you re-rungenerate-mpd-conf.shagain later (e.g. to add a new USB device) and accept its offer to copy the result into/etc/mpd.conf, that overwrites whatever those two scripts added — it backs up the file first, but you'd need to re-run them again afterward to restore Bluetooth/EQ output blocks.
setup-unattended-upgrades.sh— installs and configuresunattended-upgradesfor automatic security updates.grant-passwordless-sudo.sh— adds the invoking user to thesudogroup and grants passwordless sudo (/etc/sudoers.d/nopasswd_for_user). Run withsudoas the target user, not directly as root.install-apps.sh— creates~/binfor the invoking user and installsmpc, then prompts (30 second timeout, defaults to No) whether to also install Docker viainstall-docker.sh(which itself sets up themkdcCompose-project helper, prompting for its root directory on first run and remembering it ininstall-docker.confafterward). Other scripts assume~/binandmpcare present; only proceed with Docker-dependent scripts if you accepted the Docker install.setup-mergerfs.sh— readsMERGERFS_SOURCE_DIRS/MERGERFS_TARGET_DIRfrommpd-audio.conf(see Configuration above; refuses to run if they're still the placeholder values). Installs MergerFS and registers amergerfs-pool.servicesystemd unit that pools multiple source directories into one mount point (e.g. for a merged music library).build-mpd.sh— compiles and installs MPD from source (version fromMPD_VERSIONinmpd-audio.conf, default0.24.13) with a broad set of input/output/decoder plugins enabled.setup-bluetooth-audio.sh [MAC_ADDRESS](optional) — pairs a Bluetooth A2DP speaker/receiver and installs BlueALSA so it can be added as a localaudio_outputinmpd.conf, alongside or instead of USB/PCH. IfMAC_ADDRESSis omitted, it scans for nearby devices (15 seconds) and prompts for one (15 second timeout). If/etc/mpd.confalready exists, it backs it up and appends a newaudio_outputblock for the device directly (skipped if one for that MAC is already there, so it's safe to re-run); otherwise it just prints the block, sincegenerate-mpd-conf.sh(next) will detect this now-paired device and offer to include it in thempd.confit generates. On first setup, run this beforegenerate-mpd-conf.sheither way: that script only offers Bluetooth devices that are already paired.generate-mpd-conf.sh— detects Creative/Sound Blaster USB audio devices, the built-in analog (PCH) output, and Bluetooth A2DP speakers already paired via the previous step (or manually withbluetoothctl; skipped with a note ifbluetoothctlisn't installed), and writes a template./mpd.conf(network binding, socket, zeroconf/mDNS advertisement, log file, state persistence, auto-update on library changes, stickers, playlists, ReplayGain, symlink-following for MergerFS pools, HTTP stream output, and local ALSA output(s)) using the paths/ports frommpd-audio.conf. Prompts once whether to enable software mixing (mixer_type "software") across all local ALSA outputs for a consistent volume curve, and, per Bluetooth device found, whether to include it. Each prompt times out (defaulting to No) if left unanswered. Does not need root (only the final copy step does, if/etc/mpd.confisn't already writable by you). Finally prompts whether to copy the result to/etc/mpd.confnow; if it already exists there, it's backed up first, sincesetup-bluetooth-audio.sh/setup-alsa-equalizer.shmay have edited it directly since the last time you ran this script. Declining (or the timeout) leaves it for you to review and copy yourself, same as before.install-mympd.sh— clones, builds, and installs myMPD (web UI for MPD) from source, and registers it as themympdsystemd service.install-mpdris2.sh(optional) — run aftermpd.confis generated and installed. Builds and installs mpDris2 from source and writes~/.config/mpDris2/mpDris2.conffor the invoking user, using themusic_directoryread from/etc/mpd.conf.setup-log-rotation.sh— run aftermpd.confis installed to/etc/mpd.conf, since that's what setslog_file. Installs alogrotatepolicy forMPD_LOG_FILE(frommpd-audio.conf, the same valuegenerate-mpd-conf.shused).install-mpd2chromecast.sh(optional) — clones, installs, and runs mpd2chromecast as a systemd service, relaying MPD playback to Chromecast/Google Home devices on the LAN. Its "Cast MPD Output Stream" mode uses thehttpdoutput already inmpd.conf.setup-alsa-equalizer.sh [SLAVE_DEVICE](optional) — run aftermpd.confis installed to/etc/mpd.conf. Wraps output device(s) with a 10-band ALSA equalizer (libasound2-plugin-equal), and installs anmpd-eqhelper for saving/loading named EQ profiles as plain text, plus built-in presets (mpd-eq load rock,mpd-eq presetsto list them all) matching alsaequal-web-api's browser/HTTP presets exactly. With no argument, it reads every localalsaaudio_outputalready in/etc/mpd.conf(PCH/USB/BlueALSA - whichevergenerate-mpd-conf.shconfigured), wraps all of them (sharing one EQ sompd-eq/eqctlcontrol every output at once), and rewrites each block'sdevicein/etc/mpd.confto point at its new wrapped PCM (backing up/etc/mpd.conffirst; already-wrapped devices are skipped, so it's safe to re-run). PassingSLAVE_DEVICEexplicitly wraps only that one device as plainequal, and leavesmpd.conffor you to update yourself, same as before.install-alsaequal-web-api.sh(optional) — run aftersetup-alsa-equalizer.sh. Clones (or updates) alsaequal-web-api to~/alsaequal-web-apiand runs its own installer, which sets up a browser/HTTP front-end (port 5000) for applying the same named EQ presets asmpd-eq, as a systemd service (eqctl). Interactive: on first install it prompts for the HTTP Basic Auth username/password the service will require (rejecting blank values or the placeholderchangeme), so have those ready.install-gpodder-cli.sh [DOWNLOAD_DIR](optional) — installsgpo, the text-mode CLI for gPodder, plus operation helper scripts (gpo-login,gpo-update,gpo-download,gpo-subscribe,gpo-unsubscribe,gpo-list,gpo-info,gpo-search,gpo-toplist) into the invoking user's~/bin.DOWNLOAD_DIR(arg, or prompted, or left blank for gpo's own default) can point episode downloads at, e.g., a path under the MPD music library. Unrelated to MPD itself otherwise — useful if this box also manages podcast downloads.
| Script | Root? | Purpose |
|---|---|---|
menu.sh |
— | Interactive menu that runs the other scripts. |
setup-unattended-upgrades.sh |
yes | Enable automatic security updates. |
grant-passwordless-sudo.sh |
yes (via sudo) | Add invoking user to sudo group with NOPASSWD. |
install-apps.sh |
yes (via sudo) | Create invoking user's ~/bin; install mpc; prompts whether to delegate to install-docker.sh. |
install-docker.sh |
yes (via sudo) | Install Docker CE from the official apt repo; add invoking user to the docker group; add the mkdc helper to their ~/.bashrc. |
setup-mergerfs.sh |
yes | Pool storage directories with MergerFS as a systemd service. |
build-mpd.sh |
yes | Compile and install MPD from source (meson/ninja). |
setup-bluetooth-audio.sh |
yes | Pair a Bluetooth A2DP device, install BlueALSA, and add/update its audio_output in mpd.conf if the file exists. |
generate-mpd-conf.sh |
no* | Detect audio hardware, generate ./mpd.conf, and optionally copy it to /etc/mpd.conf (*needs root for that step if not already writable). |
install-mympd.sh |
yes | Build/install myMPD and register its systemd service. |
install-mpdris2.sh |
yes (via sudo) | Build/install mpDris2 and write its per-user config. |
setup-log-rotation.sh |
yes | Install a logrotate policy for MPD's log file. |
install-mpd2chromecast.sh |
yes (via sudo) | Install mpd2chromecast and register it as a systemd service for Chromecast/Google Home playback. |
setup-alsa-equalizer.sh |
yes | Wrap all (or one, if given) output device(s) with a 10-band ALSA EQ, updating mpd.conf to match, and install the mpd-eq save/load helper. |
install-alsaequal-web-api.sh |
yes (via sudo) | Clone/update and install alsaequal-web-api as the eqctl systemd service. |
install-gpodder-cli.sh |
yes (via sudo) | Install gpo (gPodder CLI) and operation helper scripts into the invoking user's ~/bin. |
- Scripts that need the real invoking user (not root) rely on
$SUDO_USER, so they must be run withsudofrom a regular user's shell, not while already logged in as root. build-mpd.shandinstall-mympd.shbuild from source and install to the default prefix (typically/usr/local); re-runninginstall-mympd.shpulls the latest upstream changes and rebuilds.generate-mpd-conf.shalways writes to./mpd.confin the current directory first, so you can review it either way. Copying it to/etc/mpd.confis opt-in (a y/n prompt, 30 second timeout, defaults to No) rather than automatic.generate-mpd-conf.shonly offers Bluetooth devices already paired (e.g. viasetup-bluetooth-audio.shor manually withbluetoothctl) that advertise the A2DP "Audio Sink" service — it doesn't pair new devices itself.- Edit
mpd-audio.conf(see Configuration above) before runningsetup-mergerfs.sh,build-mpd.sh,generate-mpd-conf.sh, orsetup-log-rotation.sh— all four read their settings from there instead of hardcoded values in the scripts. setup-bluetooth-audio.shonly editsmpd.confif/etc/mpd.confalready exists (backing it up first, likesetup-alsa-equalizer.sh); otherwise it prints theaudio_output {}block instead, since there's nothing yet to append to. Its pairing flow relies on Simple Secure Pairing (no PIN prompt); devices that require a PIN or on-device confirmation must be paired manually withbluetoothctl.setup-log-rotation.shonly covers MPD's own log (/var/lib/mpd/log, set vialog_fileinmpd.conf). myMPD and mpDris2 run under systemd with no dedicated log file in this setup, so their output goes to the journal, which journald rotates on its own. It usescopytruncateinstead of a reload signal, since MPD has no documented signal for reopening its log file after rotation.install-mpd2chromecast.shclones/updates~<user>/mpd2chromecastand installs Python packages via apt where available, falling back topip3(with--break-system-packagesonly when the PEP 668EXTERNALLY-MANAGEDmarker is present) — a more portable check than upstream's own installer, which assumes an integerVERSION_IDfrom/etc/os-releaseand breaks on Ubuntu's22.04-style versioning. Its web control interface (device selection, cast mode) listens on port8090.setup-alsa-equalizer.shwrites/overwrites/etc/asound.conf, backing up any existing copy first (/etc/asound.conf.bak.<timestamp>) since it's a shared system-wide file. In auto-detect mode (no argument) it also backs up and rewrites/etc/mpd.conf(/etc/mpd.conf.bak.<timestamp>) — the only script in this project that touchesmpd.confdirectly, everywhere else prints instructions for you to apply by hand. Every wrapped output shares onectl.equalcontrol surface, sompd-eq/alsaequal-web-apiapply one EQ curve to all of them at once rather than each having independent settings.libasound2-plugin-equal(the package formerly namedalsaequal) stores band gains in an opaque binary file, editable only through an ALSA mixer —mpd-eqworks around that by round-tripping the same controls throughamixeras plain "name:value" text profiles under/var/lib/mpd/alsaequal/profiles/.install-alsaequal-web-api.shis a thin wrapper: it only clones/updates the repo and delegates to that project's owninstall.sh, rather than duplicating its apt/systemd setup here. It doesn't wire the ALSAequaldevice itself — that'ssetup-alsa-equalizer.sh's job, and must run first.install-gpodder-cli.shavoids Debian'sgpodderapt package (bundles the GTK GUI, pulling ingir1.2-gtk-3.0/python3-gi/etc. even thoughgpoitself never imports them) by installing from source viapipxinstead, withGPODDER_INSTALL_UIS=cliandpip install --no-depsto skip gpodder's declaredPyGObject/dbus-pythondependencies (GTK-only; would otherwise need GObject-Introspection/ D-Bus build headers). The three packagesgpoactually imports (podcastparser,mygpoclient,requests) are added afterward viapipx inject.gpohas no dedicated login command —gpo-loginconfigures gpodder.net credentials viagpo set mygpo.*instead, the closest real equivalent. Similarly, the download directory isn't agpo setkey either — it's theGPODDER_DOWNLOAD_DIRenvironment variable (default~/gPodder/Downloads), which is why a customDOWNLOAD_DIRgets baked intogpo-update/gpo-downloadas anexportline rather than passed togpoas a config setting.
Contributions are welcome!
- Bug reports: Open an issue.
- Everything else (questions, feature requests, ideas, general discussion): Use Discussions.
- Pull requests are welcome for bug fixes or discussed features.
This project is licensed under the GNU General Public License v3.0.
See LICENSE for more information.