/
/
/
1#!/bin/sh
2set -e
3
4# Parse configuration
5server_repo=$(cat /data/options.json | jq -r .server_repo)
6frontend_repo=$(cat /data/options.json | jq -r .frontend_repo)
7
8echo ""
9echo "-----------------------------------------------------------"
10echo "Music Assistant Developer Add-on"
11echo "-----------------------------------------------------------"
12echo ""
13
14# Check if server_repo is empty or null
15if [ -z "$server_repo" ] || [ "$server_repo" = "null" ]; then
16 build_from_source=false
17 echo "No server_repo specified - using latest nightly release from GitHub"
18else
19 build_from_source=true
20 echo "Building from source using server_repo: $server_repo"
21fi
22echo ""
23
24# Function to parse repository reference and return owner/repo@ref
25parse_repo_ref() {
26 local input="$1"
27 local default_owner="$2"
28 local default_repo="$3"
29
30 # If input starts with "pr-", convert to pull request reference
31 if echo "$input" | grep -q "^pr-"; then
32 pr_number=$(echo "$input" | sed 's/pr-//')
33 echo "${default_owner}/${default_repo}@refs/pull/${pr_number}/head"
34 return
35 fi
36
37 # If input contains "/" (fork reference)
38 if echo "$input" | grep -q "/"; then
39 # Check if it already has @ for branch
40 if echo "$input" | grep -q "@"; then
41 echo "$input"
42 else
43 # It's just owner/repo, use default branch
44 echo "${input}@main"
45 fi
46 return
47 fi
48
49 # Otherwise, it's just a branch/commit reference
50 echo "${default_owner}/${default_repo}@${input}"
51}
52
53# Function to build git URL from parsed reference
54build_git_url() {
55 local parsed="$1"
56 echo "git+https://github.com/${parsed}"
57}
58
59# Activate virtual environment
60. $VIRTUAL_ENV/bin/activate
61
62echo "-----------------------------------------------------------"
63echo "Step 1: Installing Music Assistant Server"
64echo "-----------------------------------------------------------"
65echo ""
66
67if [ "$build_from_source" = true ]; then
68 # Parse server repository reference
69 server_ref=$(parse_repo_ref "$server_repo" "music-assistant" "server")
70 server_url=$(build_git_url "$server_ref")
71
72 echo "Server repository: $server_ref"
73 echo "Server URL: $server_url"
74 echo ""
75
76 # Build requirements URL from the same reference
77 # Convert git reference to raw GitHub URL for requirements file
78 # Format: owner/repo@ref -> https://raw.githubusercontent.com/owner/repo/ref/requirements_all.txt
79 req_owner=$(echo "$server_ref" | cut -d'/' -f1)
80 req_repo=$(echo "$server_ref" | cut -d'/' -f2 | cut -d'@' -f1)
81 req_ref=$(echo "$server_ref" | cut -d'@' -f2)
82 requirements_url="https://raw.githubusercontent.com/${req_owner}/${req_repo}/${req_ref}/requirements_all.txt"
83
84 echo "Installing dependencies from: $requirements_url"
85 echo ""
86
87 # Install dependencies from the branch's requirements_all.txt
88 uv pip install \
89 --no-cache \
90 --link-mode=copy \
91 -r "$requirements_url"
92
93 echo "â Dependencies installed"
94 echo ""
95
96 # Install server from specified repository
97 uv pip install \
98 --no-cache \
99 --link-mode=copy \
100 "$server_url"
101else
102 # Install latest nightly wheel from GitHub releases
103 echo "Fetching latest nightly release from GitHub..."
104 echo ""
105
106 # Get the latest pre-release info from GitHub API
107 # Write to temp file to avoid control character issues when passing through shell variables
108 tmp_releases="/tmp/releases.json"
109 curl -s "https://api.github.com/repos/music-assistant/server/releases?per_page=10" > "$tmp_releases"
110 release_tag=$(jq -r '[.[] | select(.prerelease == true)] | first | .tag_name' < "$tmp_releases")
111 wheel_url=$(jq -r '[.[] | select(.prerelease == true)] | first | .assets[] | select(.name | endswith(".whl")) | .browser_download_url' < "$tmp_releases")
112 rm -f "$tmp_releases"
113
114 if [ -z "$wheel_url" ] || [ "$wheel_url" = "null" ]; then
115 echo "ERROR: Could not find wheel in latest nightly release"
116 echo "Falling back to stable PyPI release..."
117 uv pip install \
118 --no-cache \
119 --link-mode=copy \
120 music-assistant
121 else
122 echo "Found nightly release: $release_tag"
123 echo "Wheel URL: $wheel_url"
124 echo ""
125 echo "Downloading and installing nightly wheel..."
126 uv pip install \
127 --no-cache \
128 --link-mode=copy \
129 "$wheel_url"
130 fi
131fi
132
133echo ""
134echo "â Server installation complete"
135echo ""
136
137# Check if we should build frontend - this is independent of server source
138build_frontend=false
139if [ -n "$frontend_repo" ] && [ "$frontend_repo" != "null" ]; then
140 build_frontend=true
141 # Parse frontend repository reference
142 frontend_ref=$(parse_repo_ref "$frontend_repo" "music-assistant" "frontend")
143
144 echo "-----------------------------------------------------------"
145 echo "Step 2: Building and Installing Music Assistant Frontend"
146 echo "-----------------------------------------------------------"
147 echo ""
148 echo "Frontend repository: $frontend_ref"
149 echo ""
150else
151 echo "-----------------------------------------------------------"
152 echo "Step 2: Skipping Frontend Build"
153 echo "-----------------------------------------------------------"
154 echo ""
155 echo "No frontend_repo specified - using frontend bundled with server"
156 echo ""
157fi
158
159if [ "$build_frontend" = true ]; then
160
161# Extract owner, repo, and ref from parsed reference
162frontend_owner=$(echo "$frontend_ref" | cut -d'/' -f1)
163frontend_repo_name=$(echo "$frontend_ref" | cut -d'/' -f2 | cut -d'@' -f1)
164frontend_branch=$(echo "$frontend_ref" | cut -d'@' -f2)
165
166# Create temporary directory for frontend build
167frontend_dir="/tmp/frontend-build"
168rm -rf "$frontend_dir"
169mkdir -p "$frontend_dir"
170
171echo "Cloning frontend repository..."
172cd "$frontend_dir"
173
174# Clone the repository
175git clone --depth 1 --branch "$frontend_branch" \
176 "https://github.com/${frontend_owner}/${frontend_repo_name}.git" . 2>/dev/null || \
177 (git clone "https://github.com/${frontend_owner}/${frontend_repo_name}.git" . && \
178 git checkout "$frontend_branch")
179
180# Ensure we have the absolute latest changes from the remote
181git fetch --depth 1 origin "$frontend_branch"
182git reset --hard FETCH_HEAD
183
184echo "â Frontend cloned ($(git rev-parse --short HEAD))"
185echo ""
186
187# Check if package.json exists (verify it's a valid frontend repo)
188if [ ! -f "package.json" ]; then
189 echo "ERROR: package.json not found in frontend repository!"
190 echo "This doesn't appear to be a valid Music Assistant frontend repository."
191 exit 1
192fi
193
194echo "Installing frontend dependencies..."
195# Try to remount /tmp with exec if it's mounted noexec
196if mount | grep -q "on /tmp.*noexec"; then
197 echo "Detected /tmp mounted with noexec, attempting to remount..."
198 mount -o remount,exec /tmp 2>/dev/null || echo "Warning: Could not remount /tmp"
199fi
200# Use persistent cache dir so yarn doesn't re-download packages on every restart
201yarn config set cache-folder /data/.yarn-cache
202yarn install --frozen-lockfile --network-timeout 300000
203
204echo "â Dependencies installed"
205echo ""
206
207echo "Building frontend..."
208yarn build
209
210echo "â Frontend build complete"
211echo ""
212
213# Check if the Python package structure exists
214if [ ! -f "setup.py" ] && [ ! -f "pyproject.toml" ]; then
215 echo "ERROR: No Python package configuration found!"
216 echo "Frontend repository must be installable as a Python package."
217 exit 1
218fi
219
220echo "Installing frontend as Python package..."
221cd "$frontend_dir"
222uv pip install --no-cache --link-mode=copy .
223
224echo "â Frontend installation complete"
225echo ""
226
227# Cleanup
228cd /
229rm -rf "$frontend_dir"
230
231fi # End of build_frontend check
232
233echo "-----------------------------------------------------------"
234echo "Starting Music Assistant"
235echo "-----------------------------------------------------------"
236echo ""
237
238# export jemalloc path
239for path in /usr/lib/*/libjemalloc.so.2; do
240 [ -f "$path" ] && export LD_PRELOAD="$path" && break
241done
242# Start Music Assistant
243exec mass --data-dir /data --cache-dir /data/.cache
244
245