diff --git a/README.md b/README.md index 7812974..29f48f9 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ That's it. The wizard walks you through: domain), or no bundled proxy when you publish ports / run your own 8. The first administrator account 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 everything, and waits for the API health check before declaring success. diff --git a/deploy.sh b/deploy.sh index 8a534ad..3b2a4b7 100755 --- a/deploy.sh +++ b/deploy.sh @@ -39,6 +39,7 @@ CFG_ADMIN_CREATE="no"; CFG_ADMIN_USER=""; CFG_ADMIN_PASS="" CFG_ML_URL="" CFG_ACOUSTID_KEY="" CFG_MUSICBRAINZ_OWNER_EMAIL="" +CFG_YOUTUBE="no"; CFG_YOUTUBE_COOKIES_HOST_PATH="./data/youtube" CFG_JWT_SECRET="" # ========================================================================== @@ -195,6 +196,19 @@ step_enrichment() { 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() { if [[ "$CFG_WEBUI" == "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_redis):" "$rdl" 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)" echo 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_info "$(t done_config): ${ENV_FILE}" [[ "$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)" } @@ -247,6 +266,7 @@ run_wizard() { step_admin step_ml step_enrichment + step_youtube gen_hex 32; CFG_JWT_SECRET="$SECRET" diff --git a/i18n/en.sh b/i18n/en.sh index b21b34e..a67bc12 100644 --- a/i18n/en.sh +++ b/i18n/en.sh @@ -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_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 --------------------------------------------------------- MSG[summary_title]="Summary (secrets hidden)" MSG[summary_services]="Services" @@ -117,6 +127,8 @@ MSG[summary_access]="Access" MSG[confirm_start]="Generate config and start now?" MSG[embedded]="built-in" MSG[external]="external" +MSG[enabled]="enabled" +MSG[disabled]="disabled" MSG[pull_images]="Pulling images (%s)..." MSG[pull_hint]="If pulls fail with 'unauthorized', run 'docker login git.ollyhearn.ru' first (private registry)." diff --git a/i18n/ru.sh b/i18n/ru.sh index 3a1fb0e..2eaf10f 100644 --- a/i18n/ru.sh +++ b/i18n/ru.sh @@ -106,6 +106,16 @@ MSG[acoustid_prompt]="API-ключ AcoustID (пусто — только вст MSG[musicbrainz_note]="MusicBrainz/AcoustID требуют контактный email в User-Agent, иначе запросы могут ограничиваться (throttling)." 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 --------------------------------------------------------- MSG[summary_title]="Сводка (секреты скрыты)" MSG[summary_services]="Сервисы" @@ -117,6 +127,8 @@ MSG[summary_access]="Доступ" MSG[confirm_start]="Сгенерировать конфиг и запустить сейчас?" MSG[embedded]="встроенный" MSG[external]="внешний" +MSG[enabled]="включено" +MSG[disabled]="выключено" MSG[pull_images]="Скачивание образов (%s)..." MSG[pull_hint]="Если pull падает с 'unauthorized', выполните 'docker login git.ollyhearn.ru' (приватный регистри)." diff --git a/lib/compose_gen.sh b/lib/compose_gen.sh index 9ed6dc8..342ec59 100644 --- a/lib/compose_gen.sh +++ b/lib/compose_gen.sh @@ -31,6 +31,13 @@ generate_compose() { webui_ports=" ports:${nl} - \"\${WEBUI_PORT}:80\"" 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. if [[ "$CFG_HTTPS" == "yes" ]]; then caddy_ports=" - \"80:80\"${nl} - \"443:443\"" @@ -41,6 +48,7 @@ generate_compose() { # -- assemble --------------------------------------------------------- local backend webui caddy backend="$(_frag backend.yml)" + backend="${backend//@YOUTUBE_VOLUME@/$youtube_volume}" backend="${backend//@API_PORTS@/$api_ports}" backend="${backend//@API_DEPENDS@/$api_depends}" backend="${backend//@WORKER_DEPENDS@/$worker_depends}" diff --git a/lib/env_gen.sh b/lib/env_gen.sh index a620ed1..dd58628 100644 --- a/lib/env_gen.sh +++ b/lib/env_gen.sh @@ -93,5 +93,24 @@ generate_env() { } >>"$ENV_FILE" 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" } diff --git a/lib/lifecycle.sh b/lib/lifecycle.sh index 9d4f318..84c3f1d 100644 --- a/lib/lifecycle.sh +++ b/lib/lifecycle.sh @@ -36,6 +36,17 @@ ensure_media_dir() { 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() { ui_info "$(t pull_images "$(grep -E '^MCMA_IMAGE_TAG=' "$ENV_FILE" | cut -d= -f2-)")" ui_dim "$(t pull_hint)" @@ -64,6 +75,7 @@ sys.exit(1) lifecycle_start() { local create_admin="${1:-no}" ensure_media_dir + ensure_youtube_dir lifecycle_pull local deps; deps="$(backing_services)" @@ -111,7 +123,7 @@ lifecycle_update() { 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_logs() { dc logs -f --tail=100; } diff --git a/templates/compose/backend.yml b/templates/compose/backend.yml index 73456ff..de1e4cf 100644 --- a/templates/compose/backend.yml +++ b/templates/compose/backend.yml @@ -6,6 +6,7 @@ volumes: - ${MEDIA_HOST_PATH}:${MEDIA_PATH} - transcode_cache:${TRANSCODE_CACHE_PATH} +@YOUTUBE_VOLUME@ @API_PORTS@ @API_DEPENDS@ @@ -17,4 +18,5 @@ volumes: - ${MEDIA_HOST_PATH}:${MEDIA_PATH} - transcode_cache:${TRANSCODE_CACHE_PATH} +@YOUTUBE_VOLUME@ @WORKER_DEPENDS@