Setting up the server, end to end
This page takes you from nothing to "my player is plugged into my laptop and syncing from the library on my NAS." It covers both ways of running the server and every sign-in option.
If you only want the shape of the thing first:
the machine with your music the machine you are sitting at
─────────────────────────── ──────────────────────────────
library + SQLite + ffmpeg ──HTTPS──▶ a browser tab ──USB──▶ your player
the sync engine ◀──WS──── holds the deviceStep 1 — decide how the server runs
There are two, and they are the same server — the same handlers, the same database, the same sync engine. Pick on how the machine is used, not on features.
A. The desktop app hosts it. You already run iPodRocks on the machine with your music, and you want to reach it from elsewhere sometimes. Turn on Settings → Web Server. The window and the browser talk to one library at the same time; nothing is duplicated.
B. A headless daemon. The machine has no screen, or you want it up without anyone logged in — a NAS, a home server, a container. No Electron is involved at all.
You can switch later. Both read the same data directory, so a machine can run one and then the other; just do not run both at once against the same folder.
Step 2 — start it
A. From the desktop app
Settings → Web Server → Run the web server.
- Bind address — leave
127.0.0.1if a tunnel or reverse proxy will be in front (recommended).0.0.0.0makes it reachable from your local network. - Port —
8780by default. - Public URL — fill this in once you have a real address (step 4). Sign-in callbacks are built from it.
Press Apply, then start the server. The card shows the URL and, on first run, the one-time claim token.
B. As a daemon
From a checkout:
cd ipodrocks-js
npm ci --ignore-scripts # --ignore-scripts skips Electron's binary download
npm run build
IPODROCKS_DATA_DIR=/srv/ipodrocks \
IPODROCKS_SERVER_HOST=127.0.0.1 \
IPODROCKS_SERVER_PORT=8780 \
IPODROCKS_SESSION_SECRET="$(openssl rand -base64 48)" \
npm run serverOr with Docker, using the image every release publishes to Docker Hub (linux/amd64 and linux/arm64) — no checkout needed:
docker pull jpsobral/ipodrocks-server:latest
docker run -d --name ipodrocks \
-p 127.0.0.1:8780:8780 \
-v ipodrocks-data:/data \
-v /srv/music:/music:ro \
-e IPODROCKS_SESSION_SECRET="$(openssl rand -base64 48)" \
jpsobral/ipodrocks-server:latestPrefer an image with a shell inside? Use jpsobral/ipodrocks-server:alpine instead — the same server on Alpine Linux. Flavours, tags, upgrades and using a host folder for /data are covered in Deploying the Server.
For a compose file, a systemd unit and the full environment reference, see Deploying the Server.
Set IPODROCKS_SESSION_SECRET
Without it a new one is generated at every boot, which logs everyone out on every restart.
Step 3 — claim it
The first person to sign in cannot be checked against an allowlist, because the allowlist is empty. So the server prints a one-time claim token:
┌─ First run ────────────────────────────────────────────────┐
│ This server has no owner yet. Open it in a browser and │
│ sign in with this one-time claim token: │
│ xXk3… │docker logs ipodrocks, journalctl -u ipodrocks-server, or the Settings card if the desktop app is hosting. Whoever can read that log owns the machine, so binding the first identity to it grants nothing an attacker did not already have.
If the server runs inside the desktop app, you can skip the browser: Settings → Web Server shows Create owner account under the token while nobody owns the server.
Otherwise, open the server in a browser, choose a username and a password of at least 12 characters, and paste the token. That account is the owner — the only one that can manage who else gets in. The token is consumed and never printed again.
A local password account works everywhere, including on a home network with no public hostname. If that is all you need, skip to step 6.
Step 4 — get a real HTTPS address
You need one for two independent reasons:
- No browser will let a page touch a folder on an insecure origin, so remote devices do not work over plain HTTP.
- Google, GitHub and Facebook all refuse a sign-in callback that is not a stable public HTTPS URL. None of them will accept
http://192.168.1.10:8780.
The recommended route opens no inbound port at all.
Cloudflare Tunnel
- In the Cloudflare Zero Trust dashboard, create a named tunnel.
- Add a public hostname — say
ipod.example.com— routed tohttp://127.0.0.1:8780(orhttp://ipodrocks:8780for the compose sidecar). - Run
cloudflaredon the server with the tunnel's token, or start thetunnelprofile indocker-compose.yml. - Set the public URL:
- daemon:
IPODROCKS_PUBLIC_URL=https://ipod.example.com - desktop: Settings → Web Server → Public URL
- daemon:
- Set
IPODROCKS_TRUSTED_PROXIESto the tunnel's address and nothing wider — the login rate limiter keys on the client address, and a wide-opentrust proxylets anyone forge one.
Any other reverse proxy works the same way. You can also hand the server its own certificate with IPODROCKS_TLS_CERT and IPODROCKS_TLS_KEY.
Restart the server after changing the public URL. It is read at startup, and a running server keeps the settings it started with.
Step 5 — sign-in providers (optional)
Signing in is not the same as being let in. Anyone on earth can complete a Google login against your client id and arrive at your callback with a perfectly valid profile. What decides whether they reach your library is the allowlist — always, for every provider. Setting one up does not open anything.
Credentials are environment variables, never settings. They belong to someone else's console and have no business in a file the app rewrites on every save; and a secret encrypted by the desktop app cannot be read by the daemon anyway. That applies to both ways of running the server: set them in the environment the server starts in.
Everything below assumes your public URL is https://ipod.example.com — substitute yours, scheme and all.
Google
Go to the Google Cloud console and pick or create a project.
APIs & Services → OAuth consent screen. Choose External unless you have a Workspace org. Fill in the app name and a support email. You do not need to submit for verification: add yourself (and anyone else who will sign in) under Test users and leave it in Testing.
Scopes: the defaults are enough. iPodRocks asks only for
profileandemail.Credentials → Create credentials → OAuth client ID → Web application.
Under Authorised redirect URIs add exactly:
https://ipod.example.com/api/auth/google/callbackIt must match to the character — scheme, host, port, path. A trailing slash is a different URI.
Copy the client ID and client secret into the environment:
shIPODROCKS_GOOGLE_CLIENT_ID=...apps.googleusercontent.com IPODROCKS_GOOGLE_CLIENT_SECRET=...
GitHub
Settings → Developer settings → OAuth Apps → New OAuth App (or an org's settings, for an org-owned app).
Homepage URL:
https://ipod.example.comAuthorization callback URL:
https://ipod.example.com/api/auth/github/callbackGenerate a client secret and copy both values:
shIPODROCKS_GITHUB_CLIENT_ID=... IPODROCKS_GITHUB_CLIENT_SECRET=...
GitHub shows a secret once. If you lose it, generate another.
Facebook
developers.facebook.com → My Apps → Create App, type Consumer.
Add the Facebook Login product, Web.
Under Facebook Login → Settings, Valid OAuth Redirect URIs:
https://ipod.example.com/api/auth/facebook/callbackFrom Settings → Basic, copy the App ID and App Secret:
shIPODROCKS_FACEBOOK_CLIENT_ID=... IPODROCKS_FACEBOOK_CLIENT_SECRET=...
An app in development mode only admits people listed as testers. Facebook also requires the app be Live and, for public use, reviewed — which for a personal server is usually more trouble than a local password account is worth.
Where to put them
Docker Compose — a .env beside docker-compose.yml:
IPODROCKS_PUBLIC_URL=https://ipod.example.com
IPODROCKS_SESSION_SECRET=...
IPODROCKS_GOOGLE_CLIENT_ID=...
IPODROCKS_GOOGLE_CLIENT_SECRET=...systemd — a root-owned file, not the unit:
sudo install -d -m 0750 -o root -g ipodrocks /etc/ipodrocks
sudoedit /etc/ipodrocks/server.env # EnvironmentFile= reads this
sudo chmod 0640 /etc/ipodrocks/server.env
sudo chgrp ipodrocks /etc/ipodrocks/server.envsystemctl show prints Environment= lines to any user; EnvironmentFile= contents it does not.
The desktop app hosting the server — the variables have to be in the environment the app was launched in. On Linux that is a .desktop file or a wrapper script; on macOS, launchctl setenv or launching from a shell that has them. If that sounds like more trouble than it is worth, it usually is: use a local password account, which needs no provider at all.
Restart the server after setting any of these. A provider with only half its pair set is treated as not configured — deliberately, because registering a strategy with a missing secret makes the server throw at startup with a message that names neither variable.
Checking it worked
The login page shows a button per configured provider. If a provider you set up is missing, the server did not see both variables, or it has no public URL. The startup log says so:
[server] No OAuth provider is configured. Sign in with a local password account…You can also ask Rocksy: "is the web server running, and which sign-in providers are set up?"
Step 6 — add your library
Sign in and add your library folders as usual. The folder picker browses the server's filesystem, because that is where a library lives.
That is a different question from picking a device, which is a folder on the machine you are sitting at — see below.
Scan, and optionally build a shadow library. For a remote device over anything slower than a LAN, a shadow library is the difference between a comfortable sync and an impossible one.
Step 7 — let other people in
Only the owner can. A successful Google login by someone not on the allowlist is refused, which is the entire point.
To sign in with Google yourself, open Settings → Sign-in methods in the browser and press Connect Google. That Google account then signs you in as the owner. There is no user id to look up.
To let someone else in, have them try to sign in once. The attempt is refused and appears in Settings → Web Server → Waiting for approval. Press Approve. Rocksy can do the same: "who's waiting to get in?", "let in the person who just tried". A local password account is added with "give my partner an account", or over the HTTP API:
curl -b cookies.txt https://ipod.example.com/api/auth/identities
curl -b cookies.txt https://ipod.example.com/api/auth/access-requestsTo manage accounts from the desktop app, open Settings → Web Server → Who can sign in. Add a local account (username and password), press Set password on any local account (yours included) to reset it, or Remove to cut someone off. The desktop app is the owner without signing in, because it runs on the machine holding the database. A browser signed in as the owner sees the same list.
To change your own password in a browser, go to Settings → Sign-in methods → Change password. You need your current one. Every other browser signed in to your account is signed out.
Step 8 — connect a device
On the machine with the device plugged in, open the server in Chrome, Edge or another Chromium browser, go to Devices, and press + Add Device. In a browser this always adds a remote device: there is no mount path to type, because the folder is on your machine and you pick it with your own browser's picker.
Save, then press Connect this device on its card and choose the device's root folder — the one holding Music and .rockbox.
Firefox and Safari cannot do this; neither can iOS. That is not a policy choice, it is the File System Access API not existing there, and the Add Device form says so up front.
See Devices → Remote devices for what differs from a local device.
Forgot your password
Accounts that sign in with Google, GitHub or Facebook have no password here, so this only concerns local (username and password) accounts. Pick whichever of these you can reach:
The desktop app is hosting the server. Settings → Web Server → Who can sign in → Set password on your account. No sign-in needed.
Someone else's password. If you are the owner, the same Set password button works from a browser too.
A headless server you have a shell on. On that machine, with the same
IPODROCKS_DATA_DIRthe daemon uses:shnpm run server:accounts -- list npm run server:accounts -- password <username> # Docker: docker exec -it ipodrocks node dist/main/server/cli.js password <username>The new password is asked for twice without echo (or read from one line of stdin when piped), never taken from the command line. The account's existing sessions are signed out. It is safe to run while the daemon is up.
A headless server with no shell, only its log (a hosted container, for example). Restart it with
IPODROCKS_RESET_OWNER=1. The log prints a one-time token, valid for 30 minutes. On the login page press Reset owner password, paste the token and choose a new password. You are signed in as the owner. Then remove the variable and restart. A boot without it deletes any unused token, so a forgotten variable does not leave a way in open. This resets the owner only; the owner can then reset anyone else.
All four rely on the same rule as the claim token: whoever can reach the machine's database or its log already controls the server.
Troubleshooting
"This server has no owner yet." You are signing in without the claim token, or somebody already claimed it. Check the log.
"This account is not authorized to use this server." The login worked and the allowlist refused it. Working as intended. The attempt is now under Settings → Web Server → Waiting for approval, for the owner to approve.
A provider's button is missing. Both its variables must be set, and IPODROCKS_PUBLIC_URL must be set. Restart after changing them.
redirect_uri_mismatch. The URI registered with the provider does not match <public URL>/api/auth/<provider>/callback exactly. Check the scheme, any port and the trailing slash.
Everyone is logged out after a restart. IPODROCKS_SESSION_SECRET is unset.
Social login bounces straight back to the login page. Usually the public URL does not match the address you are actually typing; the session cookie is SameSite=Lax and the origin check is built from IPODROCKS_PUBLIC_URL.
The app loads and reads fine, but saving anything says "Cross-origin request refused". The same mismatch, seen from the other side: requests that change something are accepted only from an origin this server serves. Point IPODROCKS_PUBLIC_URL at the address you type, or list it in IPODROCKS_ALLOWED_ORIGINS.
Add Device offers no folder picker / "This browser cannot hold a device." Not a Chromium browser, or not on HTTPS.
A device is listed but everything is greyed out. It belongs to the other side: a server-attached device seen from a browser, or a remote device seen from the desktop app. See Devices → Remote devices.