Unraid/Nas Entrypoint.sh hardening + unraid xml template (#4)

* Unraid/Nas Entrypoint.sh hardening + unraid xml template

* further entrypoint.sh hardening
This commit is contained in:
Harvey 2026-04-04 13:45:00 +01:00 committed by GitHub
parent eed1ce700d
commit c141b9fcdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 94 additions and 9 deletions

View file

@ -180,6 +180,8 @@ MusicSeerr stores its config in `config/config.json` inside the mapped config vo
Run `id` on your host to find your PUID and PGID values.
> **Unraid / NAS users:** Unraid defaults to `nobody:users` (PUID=99, PGID=100). If you see `chown: Operation not permitted` at startup, your volume mount is on a filesystem that rejects ownership changes (FUSE/shfs, NFS, CIFS). The container skips `chown` when the directories and their contents are already writable, so this is usually fine as long as the host paths are owned by the correct UID:GID.
### In-App Settings
| Setting | Location |

View file

@ -5,6 +5,8 @@ services:
environment:
- PUID=1000 # User ID — run `id` on your host to find yours
- PGID=1000 # Group ID — run `id` on your host to find yours
# Unraid: use 99/100 (nobody:users) unless you changed Unraid's defaults.
# If using Docker --user, PUID/PGID are ignored (user mapping is external).
- PORT=8688 # Internal port (must match the right side of "ports" below)
- TZ=Etc/UTC # Timezone — e.g. America/New_York, Europe/London
ports:

View file

@ -1,23 +1,77 @@
#!/bin/sh
set -e
umask 027
PUID=${PUID:-1000}
PGID=${PGID:-1000}
groupmod -o -g "$PGID" musicseerr 2>/dev/null || true
usermod -o -u "$PUID" musicseerr 2>/dev/null || true
case "$PUID" in ''|*[!0-9]*) echo "[init] FATAL: PUID='$PUID' is not a valid numeric UID."; exit 1;; esac
case "$PGID" in ''|*[!0-9]*) echo "[init] FATAL: PGID='$PGID' is not a valid numeric GID."; exit 1;; esac
check_writable() {
_dir="$1"
_identity="$2"
_probe="$_dir/.musicseerr_write_test_$$"
if [ -n "$_identity" ]; then
gosu "$_identity" touch "$_probe" 2>/dev/null; _rc=$?
gosu "$_identity" rm -f "$_probe" 2>/dev/null
else
touch "$_probe" 2>/dev/null; _rc=$?
rm -f "$_probe" 2>/dev/null
fi
return "$_rc"
}
if [ "$(id -u)" -ne 0 ]; then
echo "[init] Running as uid=$(id -u) gid=$(id -g) (non-root); skipping user/group setup."
for dir in /app/cache /app/config; do
mkdir -p "$dir" 2>/dev/null || true
if ! check_writable "$dir"; then
echo "[init] FATAL: $dir is not writable by uid=$(id -u)."
echo "[init] Ensure the host directory is owned by this UID/GID."
echo "[init] Run: chown $(id -u):$(id -g) <host-path>"
exit 1
fi
done
exec "$@"
fi
if ! groupmod -o -g "$PGID" musicseerr 2>/dev/null; then
echo "[init] WARNING: Could not set musicseerr group to GID=$PGID."
fi
if ! usermod -o -u "$PUID" musicseerr 2>/dev/null; then
echo "[init] WARNING: Could not set musicseerr user to UID=$PUID."
fi
TARGET_UID=$(id -u musicseerr)
TARGET_GID=$(id -g musicseerr)
echo "[init] Runtime user: musicseerr (uid=$TARGET_UID gid=$TARGET_GID)"
if [ "$TARGET_UID" != "$PUID" ]; then
echo "[init] WARNING: Requested PUID=$PUID but running as uid=$TARGET_UID (usermod may have failed)."
fi
if [ "$TARGET_GID" != "$PGID" ]; then
echo "[init] WARNING: Requested PGID=$PGID but running as gid=$TARGET_GID (groupmod may have failed)."
fi
# Only chown directories whose top-level ownership differs from the target.
# Nested mismatches from manual edits require a rebuild or manual chown.
for dir in /app/cache /app/config; do
if [ -d "$dir" ]; then
CURRENT=$(stat -c '%u:%g' "$dir")
if [ "$CURRENT" != "$TARGET_UID:$TARGET_GID" ]; then
chown -R musicseerr:musicseerr "$dir"
fi
mkdir -p "$dir" 2>/dev/null || true
if check_writable "$dir" "$TARGET_UID:$TARGET_GID"; then
continue
fi
if chown musicseerr:musicseerr "$dir" 2>/dev/null; then
echo "[init] Adjusted ownership of $dir - verifying write access."
else
echo "[init] WARNING: Could not chown $dir (mount may not support ownership changes)."
fi
if ! check_writable "$dir" "$TARGET_UID:$TARGET_GID"; then
echo "[init] FATAL: $dir is not writable by uid=$TARGET_UID gid=$TARGET_GID."
echo "[init] Common causes: FUSE/shfs (Unraid), NFS root_squash, CIFS/SMB, dropped CAP_CHOWN."
echo "[init] Fix: ensure the host directory is writable by uid=$TARGET_UID gid=$TARGET_GID."
exit 1
fi
done

27
templates/musicseerr.xml Normal file
View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Container version="2">
<Name>musicseerr</Name>
<Repository>ghcr.io/habirabbu/musicseerr:latest</Repository>
<Registry>https://github.com/habirabbu/musicseerr/pkgs/container/musicseerr</Registry>
<Network>bridge</Network>
<Privileged>false</Privileged>
<Support>https://github.com/HabiRabbu/Musicseerr/issues</Support>
<Project>https://github.com/HabiRabbu/Musicseerr</Project>
<Overview>MusicSeerr is a self-hosted music request and discovery app built around Lidarr. Search the full MusicBrainz catalogue, request albums, stream music from Jellyfin or Navidrome, discover new music based on your listening history, and scrobble to ListenBrainz and Last.fm. Requires a running Lidarr instance.</Overview>
<Category>MediaApp:Music</Category>
<WebUI>http://[IP]:[PORT:8688]</WebUI>
<TemplateURL>https://raw.githubusercontent.com/HabiRabbu/Musicseerr/main/templates/musicseerr.xml</TemplateURL>
<Icon>https://raw.githubusercontent.com/HabiRabbu/Musicseerr/main/Images/logo_icon.png</Icon>
<Requires>A running Lidarr instance with an API key. Lidarr nightly build is recommended.</Requires>
<ExtraSearchTerms>lidarr music request scrobble listenbrainz lastfm jellyfin navidrome</ExtraSearchTerms>
<Screenshot>https://raw.githubusercontent.com/HabiRabbu/Musicseerr/main/Images/HomePage.png</Screenshot>
<Screenshot>https://raw.githubusercontent.com/HabiRabbu/Musicseerr/main/Images/ArtistPage.png</Screenshot>
<Screenshot>https://raw.githubusercontent.com/HabiRabbu/Musicseerr/main/Images/DiscoverPage.png</Screenshot>
<Config Name="WebUI Port" Target="8688" Default="8688" Mode="tcp" Description="Port the MusicSeerr web interface listens on." Type="Port" Display="always" Required="true" Mask="false">8688</Config>
<Config Name="Config" Target="/app/config" Default="/mnt/user/appdata/musicseerr/config" Mode="rw" Description="Persistent application configuration and database." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/musicseerr/config</Config>
<Config Name="Cache" Target="/app/cache" Default="/mnt/user/appdata/musicseerr/cache" Mode="rw" Description="Cover art and metadata cache." Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/musicseerr/cache</Config>
<Config Name="Music Library" Target="/music" Default="" Mode="ro" Description="Optional: mount your music library root for local file playback. Must match the root folder Lidarr uses and the Music Directory Path in MusicSeerr Settings &gt; Local Files." Type="Path" Display="advanced" Required="false" Mask="false"></Config>
<Config Name="PUID" Target="PUID" Default="99" Mode="" Description="User ID for file permissions inside the container. Run 'id' on your Unraid terminal to find your value." Type="Variable" Display="advanced" Required="false" Mask="false">99</Config>
<Config Name="PGID" Target="PGID" Default="100" Mode="" Description="Group ID for file permissions inside the container. Run 'id' on your Unraid terminal to find your value." Type="Variable" Display="advanced" Required="false" Mask="false">100</Config>
<Config Name="Timezone" Target="TZ" Default="Europe/London" Mode="" Description="Your timezone. Examples: America/New_York, Europe/Berlin, Asia/Tokyo." Type="Variable" Display="always" Required="false" Mask="false">Europe/London</Config>
</Container>