A lightweight, rootless chroot launcher for running Apple Music FairPlay DRM decrypt binaries on Linux.
sidecar manages the lifecycle of an Android-based decrypt daemon inside a chroot environment, providing PTY-based output forwarding, port readiness detection, graceful restart, and user namespace isolation — all without requiring root privileges.
┌──────────────────────────────────────┐
│ sidecar (39 KB) │
│ │
SIGUSR1 ──────────────►│ ┌─ signal handler ────────────┐ │
SIGTERM ──────────────►│ │ forward / grace / restart │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─ user namespace ────────────────┐ │
│ │ unshare(NEWUSER | NEWNS) │ │
│ │ uid 1000 → 0 (inside) │ │
│ │ bind-mount /dev, /proc, /sys │ │
│ │ │ │
│ │ ┌─ chroot ./rootfs ──────────┐ │ │
│ │ │ /system/bin/main │ │ │
│ │ │ ├─ TCP :47010 (decrypt) │ │ │
│ │ │ └─ TCP :47020 (m3u8) │ │ │
│ │ └─────────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌─ PTY ──────────────────────────┐ │
│ │ openpty() → line-buffered │ │
│ │ child stdout → parent stdout │ │
│ └────────────────────────────────┘ │
│ │
│ ┌─ port probe ───────────────────┐ │
│ │ --wait-ports 47010,47020 │ │
│ │ non-blocking TCP connect │ │
│ │ 200ms tick, interleaved w/PTY │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
This project is inspired by and builds upon the pioneering work of the Apple Music decrypt community:
- zhaarey/wrapper — the original wrapper binary for running FairPlay decrypt on Linux
- glomatico/wrapper-v2 — Rust-based supervisor with Docker isolation
- WorldObservationLog/AppleMusicDecrypt — comprehensive Apple Music decrypt toolkit
sidecar addresses several limitations encountered when deploying wrapper in production environments — particularly around rootless operation, observability, and process lifecycle management.
| Metric | wrapper | wrapper-v2 | sidecar v4 |
|---|---|---|---|
| Cold startup | 2338 ms | ~5000-8000 ms (est.) | 2163 ms |
| Decrypt throughput | 15.0 MB/s | ~15 MB/s | 15.3 MB/s |
| Memory | 53.8 MB | ~150-200 MB (est.) | 52.3 MB |
| Size | 20 KB binary | ~200-500 MB image | 35 KB binary |
| Container required | No | Docker/podman | No |
| Build deps | none (prebuilt) | Rust + NDK + CMake | cc + libutil |
| Build time | n/a | ~15 min | < 1 sec |
| Source | closed | Rust + C++ (open) | C 935 lines (open) |
| Root required | No | No (container) | No (user namespace) |
| PTY output | No | No | Yes |
| Port readiness | No | Rust supervisor | --wait-ports |
| Graceful restart | No | Supervisor restart | SIGUSR1 (in-process) |
| HTTP API | No | Yes | No (aria server provides) |
wrapper-v2 data marked (est.) is based on Docker/Rust architecture overhead analysis, not direct measurement. wrapper and sidecar v4 data is from controlled benchmarks on Dell R730 (80 cores, 94 GB RAM).
make
# or directly:
cc -O2 -Wall -Wextra -o sidecar sidecar.c -lutilRequirements: Linux with unprivileged_userns_clone enabled (default on most distributions):
cat /proc/sys/kernel/unprivileged_userns_clone # should be 1./sidecar --rootfs ./rootfs --bin /system/bin/main \
--wait-ports 47010,47020 --verbose \
-- -M 47020 -D 47010 -F./sidecar --rootfs ./rootfs --bin /system/bin/main \
--wait-ports 47010,47020 --verbose \
-- -M 47020 -D 47010 --login=user@host:passfor i in 0 1 2 3; do
./sidecar --rootfs ./rootfs --bin /system/bin/main \
--wait-ports $((47010+i)),$((47020+i)) --verbose \
-- -M $((47020+i)) -D $((47010+i)) -F &
donesudo ./sidecar --no-userns --rootfs ./rootfs --bin /system/bin/main \
--wait-ports 47010,47020 -- -M 47020 -D 47010 -Fkill -USR1 $(pgrep -f sidecar)./sidecar --json-events --rootfs ./rootfs --bin /system/bin/main \
-- -M 47020 -D 47010 -F
# stderr output:
# {"t":12345,"event":"chroot_ready"}
# {"t":12350,"event":"child_forked","pid":1234}
# {"t":12360,"event":"port_probe_first","port":47010}
# {"t":12361,"event":"port_probe_first","port":47020}
# {"t":12361,"event":"port_ready_all"}| Flag | Default | Description |
|---|---|---|
--rootfs PATH |
./rootfs |
Path to the Android rootfs directory |
--bin PATH |
/system/bin/main |
Executable to run inside chroot |
--cwd-in-chroot PATH |
/ |
Working directory after chroot |
--userns |
(on) | Use user namespace (no root needed) |
--no-userns |
Use real chroot (needs root, legacy mode) | |
--wait-ports P[,P..] |
TCP ports to probe for readiness | |
--wait-timeout N |
120 |
Seconds to wait for ports before giving up |
--ready-fd N |
Write \1\n on this fd when ports are ready |
|
--grace-secs N |
10 |
Seconds between SIGTERM and SIGKILL |
--pid-file PATH |
Write child PID to this file | |
--no-pty |
Don't allocate PTY (legacy buffered mode) | |
--no-dns-seed |
Don't copy DNS files into chroot | |
--json-events |
Emit structured JSON events on stderr | |
--verbose, -v |
Log setup steps to stderr | |
--help, -h |
Show help |
Everything after -- is passed through to the child binary verbatim.
The child binary exposes two raw TCP ports:
- Port 47010 — decrypt: pipelined stream-cipher channel for sample-by-sample FairPlay decryption
- Port 47020 — m3u8: resolves an Apple Music
adamIdto an enhanced-HLS master playlist URL
Both are locally-unauthenticated, length-prefixed binary protocols. See aria's PROTOCOL.md for the full byte-level specification.
# aria_sidecar.py manages N sidecar instances for parallel decryption
python3 aria_sidecar.py -F --instances 4[Unit]
Description=sidecar FairPlay decrypt
[Service]
Type=notify
NotifyAccess=all
ExecStart=/usr/local/bin/sidecar \
--rootfs /opt/am/rootfs \
--bin /system/bin/main \
--wait-ports 47010,47020 \
--ready-fd 3 \
--json-events \
-- -M 47020 -D 47010 -F
ExecReload=/bin/kill -USR1 $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetTested on Dell PowerEdge R730 (80 cores, 94 GB RAM, Ubuntu 22.04).
| Metric | wrapper_new | sidecar v4 | Delta |
|---|---|---|---|
| Cold startup (median) | 2338 ms | 2163 ms | sidecar 7.5% faster |
| Pipelined decrypt | 3.18s (15.0 MB/s) | 3.12s (15.3 MB/s) | tie |
| Memory (main) | 53.8 MB | 52.3 MB | sidecar 3% less |
| Binary size | 20 KB | 35 KB | wrapper smaller (closed) |
| 5-track sequential | 5/5 | 5/5 | tie |
| Source code | closed binary | 935 lines C | sidecar open |
| PTY output | no | yes | sidecar only |
| Port readiness | no | --wait-ports | sidecar only |
| Graceful restart | no | SIGUSR1 | sidecar only |
Key optimizations in v4:
- Namespace reduced to
CLONE_NEWUSER | CLONE_NEWPID(removedCLONE_NEWNS) - Port probe tick 200ms to 50ms
- Skip DNS seed and bind-mount in userns mode
- Binary stripped (
-s)
Full methodology: docs/BENCHMARK.md
MIT — see LICENSE.