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:
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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' (приватный регистри)."
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
@@ -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
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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@
|
||||||
|
|||||||
Reference in New Issue
Block a user