|
|
hai 1 mes | |
|---|---|---|
| systemd | hai 1 mes | |
| web | hai 1 mes | |
| README.md | hai 1 mes | |
| proxmox-mount.md | hai 1 mes | |
| setup.sh | hai 1 mes | |
| update.sh | hai 1 mes |
Self-hosted YouTube archival system for Proxmox LXC (Alpine Linux). Downloads YouTube content as H.264/AAC MKV video or audio-only files with embedded metadata, written directly into a Jellyfin-compatible folder structure.
Repository: https://gogs.av2x.dev/av2x/yaar
Shows/<Series>/Season NN/SNNENN - <Episode>.mkvMovies/<Title> (Year).mkv (flat — no per-movie folder)Music/<Artist>/<Album>/<Track>.<ext> (album optional)/nas/jellyfin, containing Movies/, Shows/, and Music/YAAR installs entirely inside your Alpine LXC container. The only host-side step is attaching the Jellyfin bind-mount.
See proxmox-mount.md for the full guide. In short, on the Proxmox host shell:
pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin
pct restart <CTID>
Replace /mnt/nas/jellyfin with wherever your media lives on the host. The
mp=/nas/jellyfin target must stay exactly as-is — YAAR expects that path.
Or via the web UI: Container → Resources → Add → Mount Point, host path
/mnt/nas/jellyfin, container path /nas/jellyfin, read-only off.
One line, run as root inside the container:
curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/setup.sh | sh
Or trigger it from the Proxmox host without entering the container:
pct exec <CTID> -- sh -c 'curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/setup.sh | sh'
setup.sh installs Python, ffmpeg, yt-dlp, Flask and Gunicorn, the app files,
an OpenRC service, the weekly yt-dlp auto-update cron, and the yaar-update
command. It refuses to run on the Proxmox host and fetches any files it needs
directly from the repo.
When it finishes, open http://<container-ip>:7474.
Pull the latest code, redeploy, and restart — with automatic backup and rollback if the new code fails to start:
yaar-update # update app + restart service
yaar-update --deps # also upgrade yt-dlp / Flask / Gunicorn
yaar-update --reboot # reboot the whole container after updating
Or the self-contained one-liner (always fetches the newest update logic too):
curl -fsSL https://gogs.av2x.dev/av2x/yaar/raw/master/update.sh | sh
The updater downloads both app files to a temp location and only deploys if
both succeed, backs up the previous versions to /opt/yaar/backups/ (keeping
the last 5), and restarts the service. If the service fails its health check on
the new code, it automatically restores the previous version so YAAR is never
left down.
rc-service yaar start
rc-service yaar stop
rc-service yaar restart
rc-service yaar status
Point Jellyfin at the library roots inside your mounted media directory:
| Library type | Path |
|---|---|
| Movies | /nas/jellyfin/Movies |
| Shows | /nas/jellyfin/Shows |
| Music | /nas/jellyfin/Music |
For the Shows library, enable NFO metadata reading so episode details from
yt-dlp populate correctly. Movies use a flat layout (Movies/<Title> (Year).mkv
with a matching .nfo sidecar), which Jellyfin scrapes by filename.
Video (series / movie):
| Setting | Value | Why |
|---|---|---|
| Video codec | H.264 (avc1) | Universal Jellyfin direct-play, no transcoding |
| Audio codec | AAC (mp4a) | Universal, no transcoding on any client |
| Container | MKV | Supports metadata, chapters, thumbnails |
| Selector | bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/…/best |
H.264+AAC first, graceful fallback |
If a video is only offered as VP9/AV1, yt-dlp falls back to the best available stream and still remuxes to MKV. Jellyfin may transcode those on weaker clients — a YouTube-side limitation, not a YAAR bug.
Music: selectable per job — m4a (AAC, default), opus, mp3, flac,
aac, or wav. Note that YouTube source audio is itself lossy (AAC/Opus), so
flac/wav won't add quality, only file size.
/opt/yaar/
web/
app.py ← Flask application
templates/index.html ← Web UI
venv/ ← Python virtualenv
jobs/ ← Persisted job JSON (queue survives restarts)
backups/ ← Previous app versions kept by yaar-update
update.sh ← Update script (also runs as `yaar-update`)
logs/
yaar.log ← Unified log (everything)
gunicorn.log ← Web server log
access.log ← HTTP access log
log_boot/<ts>.log ← One file per service start
log_mount/<ts>.log ← One file per mount check
log_archive/<id>.log ← One file per archive job (named by video id)
/nas/jellyfin/ ← Your bind-mounted media (mp0)
Movies/
Fantastic Voyage (1966).mkv
Fantastic Voyage (1966).nfo
Shows/
Kurzgesagt/
Season 01/
S01E01 - The Egg.mkv
S01E01 - The Egg.nfo
Music/
Nujabes/
Modal Soul/
03 - Luv(sic) Part 3.m4a
Environment variables (in /etc/yaar.env):
| Variable | Default | Description |
|---|---|---|
YAAR_BASE |
/opt/yaar |
App, logs, jobs, backups directory |
YAAR_MEDIA |
/nas/jellyfin |
Root media output directory (the mp0 mount) |
YAAR_PORT |
7474 |
Web UI port |
After changing any of these, restart: rc-service yaar restart.
tail -f /opt/yaar/logs/yaar.log # everything, live
ls /opt/yaar/logs/log_archive/ # per-job archive logs
cat /opt/yaar/logs/log_archive/<videoid>__*.log # a specific job's full trace
Archive logs are named by YouTube video id, so you can find a job's log by URL without knowing its internal id.
Queue is frozen / blank, console shows a SyntaxError
The served index.html is out of date. Run yaar-update and hard-refresh the
browser (Ctrl+Shift+R) to clear the cached script.
Mount banner is red / downloads fail immediately
/nas/jellyfin isn't mounted or isn't writable. Confirm the mp0 bind-mount is
attached and the container was restarted after adding it:
ls /nas/jellyfin # should list Movies/ Shows/ Music/
For unprivileged containers with permission issues, map the mount UID/GID:
pct set <CTID> -mp0 /mnt/nas/jellyfin,mp=/nas/jellyfin,uid=0,gid=0.
Download fails with "Sign in to confirm your age"
Age-restricted videos need cookies. Export cookies from a logged-in browser and
add a --cookies reference in build_ydl_opts() in app.py.
Falls back to VP9 instead of H.264 Some videos are only published in VP9/AV1. YAAR still downloads and remuxes; Jellyfin may transcode on weaker clients. YouTube-side limitation.
Port already in use
Change YAAR_PORT in /etc/yaar.env and restart the service.
The weekly cron handles this automatically, but to force it:
/opt/yaar/venv/bin/pip install --upgrade yt-dlp
rc-service yaar restart
Or just yaar-update --deps, which upgrades yt-dlp, Flask, and Gunicorn together.