feat: optional YouTube Music source (cookies volume + wizard step)

Adds a step_youtube wizard prompt (enable + cookies host folder), the @YOUTUBE_VOLUME@ token in templates/compose/backend.yml substituted in compose_gen, a YOUTUBE_ENABLED/cookies env block, ensure_youtube_dir in lifecycle, en/ru strings, and a README step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Senko-san
2026-06-14 14:04:49 +03:00
parent 4108121984
commit 3a63ced4d4
8 changed files with 89 additions and 1 deletions
+3
View File
@@ -26,6 +26,9 @@ That's it. The wizard walks you through:
domain), or no bundled proxy when you publish ports / run your own domain), or no bundled proxy when you publish ports / run your own
8. The first administrator account 8. The first administrator account
9. An optional ML service URL 9. An optional ML service URL
10. Metadata enrichment — an optional AcoustID key + contact email
11. YouTube Music source — enable search/download, with an optional folder for a
yt-dlp `cookies.txt` (mounted into the backend for restricted items)
Then it generates the config, pulls images, migrates, seeds the admin, starts Then it generates the config, pulls images, migrates, seeds the admin, starts
everything, and waits for the API health check before declaring success. everything, and waits for the API health check before declaring success.
+20
View File
@@ -39,6 +39,7 @@ CFG_ADMIN_CREATE="no"; CFG_ADMIN_USER=""; CFG_ADMIN_PASS=""
CFG_ML_URL="" CFG_ML_URL=""
CFG_ACOUSTID_KEY="" CFG_ACOUSTID_KEY=""
CFG_MUSICBRAINZ_OWNER_EMAIL="" CFG_MUSICBRAINZ_OWNER_EMAIL=""
CFG_YOUTUBE="no"; CFG_YOUTUBE_COOKIES_HOST_PATH="./data/youtube"
CFG_JWT_SECRET="" CFG_JWT_SECRET=""
# ========================================================================== # ==========================================================================
@@ -195,6 +196,19 @@ step_enrichment() {
ui_input "$(t musicbrainz_email_prompt)" "" v_email_opt; CFG_MUSICBRAINZ_OWNER_EMAIL="$UI_VALUE" ui_input "$(t musicbrainz_email_prompt)" "" v_email_opt; CFG_MUSICBRAINZ_OWNER_EMAIL="$UI_VALUE"
} }
step_youtube() {
ui_title "$(t step_youtube)"
ui_dim "$(t youtube_note)"
if ui_yesno "$(t youtube_q)" "yes"; then
CFG_YOUTUBE="yes"
ui_dim "$(t youtube_cookies_note)"
ui_input "$(t youtube_cookies_prompt)" "./data/youtube" v_nonempty
CFG_YOUTUBE_COOKIES_HOST_PATH="$UI_VALUE"
else
CFG_YOUTUBE="no"
fi
}
access_url() { access_url() {
if [[ "$CFG_WEBUI" == "yes" ]]; then if [[ "$CFG_WEBUI" == "yes" ]]; then
if [[ "$CFG_PROXY" == "yes" ]]; then if [[ "$CFG_PROXY" == "yes" ]]; then
@@ -222,6 +236,7 @@ step_summary() {
printf ' %-12s %s\n' "$(t summary_db):" "$dbl" printf ' %-12s %s\n' "$(t summary_db):" "$dbl"
printf ' %-12s %s\n' "$(t summary_redis):" "$rdl" printf ' %-12s %s\n' "$(t summary_redis):" "$rdl"
printf ' %-12s %s\n' "$(t summary_storage):" "$stl" printf ' %-12s %s\n' "$(t summary_storage):" "$stl"
printf ' %-12s %s\n' "$(t summary_youtube):" "$([[ "$CFG_YOUTUBE" == yes ]] && t enabled || t disabled)"
printf ' %-12s %s\n' "$(t summary_access):" "$(access_url)" printf ' %-12s %s\n' "$(t summary_access):" "$(access_url)"
echo echo
ui_yesno "$(t confirm_start)" "yes" || { ui_warn "$(t aborted)"; exit 0; } ui_yesno "$(t confirm_start)" "yes" || { ui_warn "$(t aborted)"; exit 0; }
@@ -232,6 +247,10 @@ step_done() {
ui_ok "$(t done_url): $(access_url)" ui_ok "$(t done_url): $(access_url)"
ui_info "$(t done_config): ${ENV_FILE}" ui_info "$(t done_config): ${ENV_FILE}"
[[ "$CFG_ADMIN_CREATE" == "yes" ]] && ui_info "$(t done_admin_login): ${CFG_ADMIN_USER}" [[ "$CFG_ADMIN_CREATE" == "yes" ]] && ui_info "$(t done_admin_login): ${CFG_ADMIN_USER}"
if [[ "$CFG_YOUTUBE" == "yes" ]]; then
ui_info "$(t done_youtube_title)"
ui_dim "$(t done_youtube_cookies "${CFG_YOUTUBE_COOKIES_HOST_PATH}/cookies.txt")"
fi
ui_dim "$(t done_commands)" ui_dim "$(t done_commands)"
} }
@@ -247,6 +266,7 @@ run_wizard() {
step_admin step_admin
step_ml step_ml
step_enrichment step_enrichment
step_youtube
gen_hex 32; CFG_JWT_SECRET="$SECRET" gen_hex 32; CFG_JWT_SECRET="$SECRET"
+12
View File
@@ -106,6 +106,16 @@ MSG[acoustid_prompt]="AcoustID API key (leave blank to use embedded tags only)"
MSG[musicbrainz_note]="MusicBrainz/AcoustID require a contact email in their User-Agent or they may throttle requests." MSG[musicbrainz_note]="MusicBrainz/AcoustID require a contact email in their User-Agent or they may throttle requests."
MSG[musicbrainz_email_prompt]="Contact email for MusicBrainz/AcoustID (leave blank to use the project's default)" MSG[musicbrainz_email_prompt]="Contact email for MusicBrainz/AcoustID (leave blank to use the project's default)"
# -- youtube source --------------------------------------------------------
MSG[step_youtube]="YouTube Music (search + download)"
MSG[youtube_note]="Lets users search YouTube Music and download tracks into the library (via yt-dlp). Search and most downloads work with no setup."
MSG[youtube_q]="Enable the YouTube Music source?"
MSG[youtube_cookies_note]="Optional: a browser cookies file (Netscape format) lets yt-dlp fetch age/region-restricted items. The folder below is mounted into the backend; drop your cookies.txt in it any time (leave default if unsure)."
MSG[youtube_cookies_prompt]="Host folder to mount for the cookies file"
MSG[summary_youtube]="YouTube"
MSG[done_youtube_title]="YouTube downloads are enabled."
MSG[done_youtube_cookies]="For restricted items, place an exported cookies.txt in: %s"
# -- summary / run --------------------------------------------------------- # -- summary / run ---------------------------------------------------------
MSG[summary_title]="Summary (secrets hidden)" MSG[summary_title]="Summary (secrets hidden)"
MSG[summary_services]="Services" MSG[summary_services]="Services"
@@ -117,6 +127,8 @@ MSG[summary_access]="Access"
MSG[confirm_start]="Generate config and start now?" MSG[confirm_start]="Generate config and start now?"
MSG[embedded]="built-in" MSG[embedded]="built-in"
MSG[external]="external" MSG[external]="external"
MSG[enabled]="enabled"
MSG[disabled]="disabled"
MSG[pull_images]="Pulling images (%s)..." MSG[pull_images]="Pulling images (%s)..."
MSG[pull_hint]="If pulls fail with 'unauthorized', run 'docker login git.ollyhearn.ru' first (private registry)." MSG[pull_hint]="If pulls fail with 'unauthorized', run 'docker login git.ollyhearn.ru' first (private registry)."
+12
View File
@@ -106,6 +106,16 @@ MSG[acoustid_prompt]="API-ключ AcoustID (пусто — только вст
MSG[musicbrainz_note]="MusicBrainz/AcoustID требуют контактный email в User-Agent, иначе запросы могут ограничиваться (throttling)." MSG[musicbrainz_note]="MusicBrainz/AcoustID требуют контактный email в User-Agent, иначе запросы могут ограничиваться (throttling)."
MSG[musicbrainz_email_prompt]="Контактный email для MusicBrainz/AcoustID (пусто — использовать значение по умолчанию)" MSG[musicbrainz_email_prompt]="Контактный email для MusicBrainz/AcoustID (пусто — использовать значение по умолчанию)"
# -- youtube source --------------------------------------------------------
MSG[step_youtube]="YouTube Music (поиск + скачивание)"
MSG[youtube_note]="Позволяет искать в YouTube Music и скачивать треки в библиотеку (через yt-dlp). Поиск и большинство загрузок работают без настройки."
MSG[youtube_q]="Включить источник YouTube Music?"
MSG[youtube_cookies_note]="Опционально: файл cookies браузера (формат Netscape) позволяет yt-dlp скачивать контент с возрастными/региональными ограничениями. Папка ниже монтируется в backend; положите cookies.txt в неё в любой момент (если не уверены — оставьте по умолчанию)."
MSG[youtube_cookies_prompt]="Папка на хосте для монтирования файла cookies"
MSG[summary_youtube]="YouTube"
MSG[done_youtube_title]="Скачивание из YouTube включено."
MSG[done_youtube_cookies]="Для контента с ограничениями положите экспортированный cookies.txt в: %s"
# -- summary / run --------------------------------------------------------- # -- summary / run ---------------------------------------------------------
MSG[summary_title]="Сводка (секреты скрыты)" MSG[summary_title]="Сводка (секреты скрыты)"
MSG[summary_services]="Сервисы" MSG[summary_services]="Сервисы"
@@ -117,6 +127,8 @@ MSG[summary_access]="Доступ"
MSG[confirm_start]="Сгенерировать конфиг и запустить сейчас?" MSG[confirm_start]="Сгенерировать конфиг и запустить сейчас?"
MSG[embedded]="встроенный" MSG[embedded]="встроенный"
MSG[external]="внешний" MSG[external]="внешний"
MSG[enabled]="включено"
MSG[disabled]="выключено"
MSG[pull_images]="Скачивание образов (%s)..." MSG[pull_images]="Скачивание образов (%s)..."
MSG[pull_hint]="Если pull падает с 'unauthorized', выполните 'docker login git.ollyhearn.ru' (приватный регистри)." MSG[pull_hint]="Если pull падает с 'unauthorized', выполните 'docker login git.ollyhearn.ru' (приватный регистри)."
+8
View File
@@ -31,6 +31,13 @@ generate_compose() {
webui_ports=" ports:${nl} - \"\${WEBUI_PORT}:80\"" webui_ports=" ports:${nl} - \"\${WEBUI_PORT}:80\""
fi fi
# YouTube cookies volume: mounted into api+worker only when the source is
# enabled. The host path comes from .env.deploy (YOUTUBE_COOKIES_HOST_PATH).
local youtube_volume=""
if [[ "$CFG_YOUTUBE" == "yes" ]]; then
youtube_volume=" - \${YOUTUBE_COOKIES_HOST_PATH}:/data/youtube"
fi
# Caddy publish ports: 80/443 with a domain (auto-HTTPS), else plain HTTP. # Caddy publish ports: 80/443 with a domain (auto-HTTPS), else plain HTTP.
if [[ "$CFG_HTTPS" == "yes" ]]; then if [[ "$CFG_HTTPS" == "yes" ]]; then
caddy_ports=" - \"80:80\"${nl} - \"443:443\"" caddy_ports=" - \"80:80\"${nl} - \"443:443\""
@@ -41,6 +48,7 @@ generate_compose() {
# -- assemble --------------------------------------------------------- # -- assemble ---------------------------------------------------------
local backend webui caddy local backend webui caddy
backend="$(_frag backend.yml)" backend="$(_frag backend.yml)"
backend="${backend//@YOUTUBE_VOLUME@/$youtube_volume}"
backend="${backend//@API_PORTS@/$api_ports}" backend="${backend//@API_PORTS@/$api_ports}"
backend="${backend//@API_DEPENDS@/$api_depends}" backend="${backend//@API_DEPENDS@/$api_depends}"
backend="${backend//@WORKER_DEPENDS@/$worker_depends}" backend="${backend//@WORKER_DEPENDS@/$worker_depends}"
+19
View File
@@ -93,5 +93,24 @@ generate_env() {
} >>"$ENV_FILE" } >>"$ENV_FILE"
fi fi
# -- YouTube Music source ---------------------------------------------
# COOKIES_HOST_PATH is consumed by the compose volume; COOKIES_PATH is the
# in-container path the backend reads (only used when the file is present).
if [[ "${CFG_YOUTUBE:-no}" == "yes" ]]; then
{
echo ""
echo "# -- YouTube Music source -----------------------------------------------"
echo "YOUTUBE_ENABLED=true"
echo "YOUTUBE_COOKIES_HOST_PATH=${CFG_YOUTUBE_COOKIES_HOST_PATH}"
echo "YOUTUBE_COOKIES_PATH=/data/youtube/cookies.txt"
} >>"$ENV_FILE"
else
{
echo ""
echo "# -- YouTube Music source (disabled) ------------------------------------"
echo "YOUTUBE_ENABLED=false"
} >>"$ENV_FILE"
fi
chmod 600 "$ENV_FILE" chmod 600 "$ENV_FILE"
} }
+13 -1
View File
@@ -36,6 +36,17 @@ ensure_media_dir() {
mkdir -p "$p" mkdir -p "$p"
} }
ensure_youtube_dir() {
# YOUTUBE_COOKIES_HOST_PATH is only present when the YouTube source is
# enabled; create the folder so the bind mount has a real host directory
# (the user drops cookies.txt into it later).
local p
p="$(grep -E '^YOUTUBE_COOKIES_HOST_PATH=' "$ENV_FILE" | cut -d= -f2-)"
[[ -n "$p" ]] || return 0
[[ "$p" = /* ]] || p="${BOOTSTRAP_DIR}/${p#./}"
mkdir -p "$p"
}
lifecycle_pull() { lifecycle_pull() {
ui_info "$(t pull_images "$(grep -E '^MCMA_IMAGE_TAG=' "$ENV_FILE" | cut -d= -f2-)")" ui_info "$(t pull_images "$(grep -E '^MCMA_IMAGE_TAG=' "$ENV_FILE" | cut -d= -f2-)")"
ui_dim "$(t pull_hint)" ui_dim "$(t pull_hint)"
@@ -64,6 +75,7 @@ sys.exit(1)
lifecycle_start() { lifecycle_start() {
local create_admin="${1:-no}" local create_admin="${1:-no}"
ensure_media_dir ensure_media_dir
ensure_youtube_dir
lifecycle_pull lifecycle_pull
local deps; deps="$(backing_services)" local deps; deps="$(backing_services)"
@@ -111,7 +123,7 @@ lifecycle_update() {
ui_ok "$(t done_title)" ui_ok "$(t done_title)"
} }
lifecycle_up() { ensure_media_dir; dc up -d; } lifecycle_up() { ensure_media_dir; ensure_youtube_dir; dc up -d; }
lifecycle_down() { dc down; } lifecycle_down() { dc down; }
lifecycle_logs() { dc logs -f --tail=100; } lifecycle_logs() { dc logs -f --tail=100; }
+2
View File
@@ -6,6 +6,7 @@
volumes: volumes:
- ${MEDIA_HOST_PATH}:${MEDIA_PATH} - ${MEDIA_HOST_PATH}:${MEDIA_PATH}
- transcode_cache:${TRANSCODE_CACHE_PATH} - transcode_cache:${TRANSCODE_CACHE_PATH}
@YOUTUBE_VOLUME@
@API_PORTS@ @API_PORTS@
@API_DEPENDS@ @API_DEPENDS@
@@ -17,4 +18,5 @@
volumes: volumes:
- ${MEDIA_HOST_PATH}:${MEDIA_PATH} - ${MEDIA_HOST_PATH}:${MEDIA_PATH}
- transcode_cache:${TRANSCODE_CACHE_PATH} - transcode_cache:${TRANSCODE_CACHE_PATH}
@YOUTUBE_VOLUME@
@WORKER_DEPENDS@ @WORKER_DEPENDS@