Document env vars and remove theme collapse button
CI / build-and-test (push) Successful in 14m7s

- Expand .env.example with full runtime, cache, Docker, and AI keys
- Add deployment secrets to .gitignore (docker-compose/.env,
  docker/.registry.env)
- Remove VIMEO_ACCESS_TOKEN from compose files; add OAuth redirect URIs
- Load registry credentials from .registry.env in deploy-img.sh
- Fail fast if protected registry lacks auth during pre-checks
- Pass DOCKER_USERNAME/PASSWORD explicitly to WSL in deploy-img.ps1
- Remove inline collapse buttons from themes-nav.component.html
This commit is contained in:
2026-09-28 08:34:07 -04:00
parent 23d5e58a8e
commit 69c722fdf9
9 changed files with 160 additions and 34 deletions
+62
View File
@@ -56,4 +56,66 @@ TWITCH_CLIENT_SECRET=your_twitch_client_secret_here
# Défaut serveur : youtube,dailymotion,twitch,peertube,odysee,rumble
# Exemple restrictif : DOWNLOAD_PROVIDERS=peertube,odysee
DOWNLOAD_PROVIDERS=youtube,dailymotion,twitch,peertube,odysee,rumble
# Nombre de téléchargements simultanés (défaut serveur : 2)
DOWNLOAD_MAX_CONCURRENT=2
# Quota disque des téléchargements, en octets (défaut serveur : 5 Gio)
# DOWNLOAD_STORAGE_QUOTA_BYTES=5368709120
# Fenêtre glissante de réinitialisation du quota, en ms (défaut : 30 jours)
# DOWNLOAD_QUOTA_WINDOW_MS=2592000000
# --- Gemini (résumé IA des commentaires / du transcript) ---
# La clé reste côté serveur ; le front interroge /api/ai/status.
# https://aistudio.google.com/apikey
# GEMINI_API_KEY=your_gemini_api_key_here
# --- Runtime / session ---
PORT=4000
NODE_ENV=production
# OBLIGATOIRE en production : secret de signature des JWT (rotations = sessions
# invalides). Générer avec : openssl rand -hex 32
JWT_SECRET=change_me_generate_a_long_random_secret
# Durées de vie des sessions
ACCESS_TTL_MIN=15
REFRESH_TTL_DAYS=2
REMEMBER_TTL_DAYS=30
# Forcer le flag Secure sur les cookies de session (auto-détecté si absent).
# À laisser à 'false' en local HTTP, 'true' derrière HTTPS.
# COOKIE_SECURE=false
# Fichier SQLite (défaut : ./db/newtube.db)
# NEWTUBE_DB_FILE=./db/newtube.db
# --- Cache & rate limits serveur ---
# TTL du cache de recherche YouTube en ms (défaut serveur : 5 min ; 30 min en prod)
YT_CACHE_TTL_MS=1800000
# Nombre max d'entrées en cache avant éviction
# YT_CACHE_MAX_ENTRIES=500
# Durée de blacklist d'une clé YouTube qui échoue, en ms (défaut : 6 h)
# YT_KEY_BAN_MS=21600000
# TTL du cache des suggestions, en ms (défaut : 5 min)
# SUGGEST_CACHE_TTL_MS=300000
# Suggestions web (favicon/duckduckgo) : '0' pour désactiver
# SUGGEST_WEB_ENABLED=1
# Rate limit des suggestions, requêtes par fenêtre
# SUGGEST_RATE_LIMIT=60
# TTL du cache transcript, en ms (défaut : 24 h)
# TRANSCRIPT_CACHE_TTL=86400000
# Rate limit du transcript, requêtes par fenêtre
# TRANSCRIPT_RATE_LIMIT=10
# TTL du cache des fiches chaînes, en ms (défaut : 6 h)
# CHANNEL_TTL_MS=21600000
# Timeout des fetches de contenu chaîne (ms)
# CHANNEL_FETCH_TIMEOUT_MS=15000
# CHANNEL_CONTENT_TIMEOUT_MS=15000
# Mémoire max du cache scrape yt-dlp, en Mo (défaut : 300)
# YT_SCRAPE_MEM_MAX=300
# TTL du cache des vidéos recommandées, en ms (défaut : 1 h)
# YT_RELATED_TTL_MS=3600000
# --- Docker (uniquement pour docker-compose/.env) ---
# TZ=America/Montreal
# Hostname du conteneur (docker-compose.yml: hostname)
# NGINX_HOSTNAME=newtube-angular
# Répertoire hôte des volumes persistants (db, assets, tmp/downloads)
# DIR_NEWTUBE=/DOCKER_CONFIG/newtube-angular
# Port hôte publié (docker-compose.local.yml uniquement, défaut : 4200)
# HOST_PORT=4200
+6
View File
@@ -41,3 +41,9 @@ tmp/
# Exports Takeout extraits localement (volumineux, données personnelles)
takeout-*/
takeout-*.zip
# Env de deploiement (contient des secrets en clair)
docker-compose/.env
# Identifiants du registre Docker (deploy-img.sh)
docker/.registry.env
+3 -3
View File
@@ -1,16 +1,16 @@
NGINX_HOSTNAME=AsusVivobook
NGINX_SERVER_IP=172.26.11.25
NGINX_HOSTNAME=newtube-angular
DIR_NEWTUBE=/DOCKER_CONFIG/newtube-angular
GEMINI_API_KEY=AIzaSyALfJiYQb6QYUXyUM2AKd7sV1nWV0JALJw
YOUTUBE_API_KEY=AIzaSyBXL5y8bRMO5WJwIhPK-fhrEPu2aROuKag
YOUTUBE_API_KEYS=["AIzaSyBXL5y8bRMO5WJwIhPK-fhrEPu2aROuKag", "AIzaSyC1rV2Er1n1FHZQoGgQhOxFc3nSlQWhgks", "AIzaSyBZxgB2s1kp9qZhfGRrp69Mdf_9krwppVI"]
VIMEO_ACCESS_TOKEN=40e09bf643b2ecddf68ed0f5d1574c72
TWITCH_CLIENT_ID=ikxrqgjivwn4rurhjy2mcfkcq62y3m
TWITCH_CLIENT_SECRET=kezgpj5vqrk96tu2x3ghmhwipg980p
# Google OAuth (connexion + import abonnements/favoris YouTube)
GOOGLE_CLIENT_ID=585708432771-in6nulj6mu0hus59m2huo7k6qsse78lq.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
OAUTH_APP_BASE_URL=http://localhost:4200
TWITCH_REDIRECT_URI=http://localhost:4200/api/oauth/twitch/callback
GOOGLE_REDIRECT_URI=http://localhost:4200/api/oauth/google/callback
YT_CACHE_TTL_MS=1800000
JWT_SECRET=Ceci-est-mon-programme-newtube-je-sais-que-c-est-moche
ACCESS_TTL_MIN=15
-1
View File
@@ -22,7 +22,6 @@ services:
- GEMINI_API_KEY=${GEMINI_API_KEY}
- YOUTUBE_API_KEY=${YOUTUBE_API_KEY}
- YOUTUBE_API_KEYS=${YOUTUBE_API_KEYS}
- VIMEO_ACCESS_TOKEN=${VIMEO_ACCESS_TOKEN}
- TWITCH_CLIENT_ID=${TWITCH_CLIENT_ID}
- TWITCH_CLIENT_SECRET=${TWITCH_CLIENT_SECRET}
- TWITCH_REDIRECT_URI=${TWITCH_REDIRECT_URI}
+5 -1
View File
@@ -16,7 +16,6 @@ services:
- GEMINI_API_KEY=${GEMINI_API_KEY}
- YOUTUBE_API_KEY=${YOUTUBE_API_KEY}
- YOUTUBE_API_KEYS=${YOUTUBE_API_KEYS}
- VIMEO_ACCESS_TOKEN=${VIMEO_ACCESS_TOKEN}
- TWITCH_CLIENT_ID=${TWITCH_CLIENT_ID}
- TWITCH_CLIENT_SECRET=${TWITCH_CLIENT_SECRET}
- DOWNLOAD_PROVIDERS=${DOWNLOAD_PROVIDERS:-youtube,dailymotion,twitch,peertube,odysee,rumble}
@@ -32,6 +31,11 @@ services:
- YT_COOKIES_FILE=${YT_COOKIES_FILE:-}
- YT_PO_TOKEN=${YT_PO_TOKEN:-}
- YT_EGRESS_PROXY=${YT_EGRESS_PROXY:-}
- TWITCH_REDIRECT_URI=${TWITCH_REDIRECT_URI}
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
- GOOGLE_REDIRECT_URI=${GOOGLE_REDIRECT_URI}
- OAUTH_APP_BASE_URL=${OAUTH_APP_BASE_URL}
- TZ=${TZ}
volumes:
- ${DIR_NEWTUBE}/assets:/app/assets
+14
View File
@@ -0,0 +1,14 @@
# Identifiants du registre Docker distant — NE PAS VERSIONNER.
# Copiez ce fichier en `.registry.env` (ignoré par git) et remplissez les valeurs.
# cp .registry.env.example .registry.env
#
# Priorité de lecture dans deploy-img.sh :
# 1. variables d'environnement DOCKER_USERNAME / DOCKER_PASSWORD
# 2. ce fichier
#
# Sous Windows, deploy-img.ps1 exécute le script dans WSL : WSL n'hérite pas des
# variables de Windows, ce fichier est donc la voie la plus fiable. Le .ps1
# exporte aussi les variables Windows qu'il trouve, en repli.
DOCKER_USERNAME=votre_utilisateur
DOCKER_PASSWORD=votre_mot_de_passe
+12
View File
@@ -29,7 +29,19 @@ try {
wsl -d Debian -- chmod +x "$wslScriptDir/deploy-img.sh"
Write-Verbose "[Étape 4] Construction de la commande Bash à exécuter."
# WSL n'hérite PAS des variables d'environnement de Windows : on les
# exporte explicitement dans la commande bash. Sans cela, un DOCKER_USERNAME
# défini dans PowerShell n'arriverait jamais dans deploy-img.sh et le push
# échouerait en "push access denied".
$innerCmd = "cd '$wslScriptDir' && ./deploy-img.sh"
foreach ($var in @('DOCKER_USERNAME', 'DOCKER_PASSWORD')) {
$value = [Environment]::GetEnvironmentVariable($var)
if ($value) {
# Échappement des caractères spéciaux bash (simple quotes)
$escaped = $value -replace "'", "'\''"
$innerCmd = "export $var='$escaped'; " + $innerCmd
}
}
Write-Verbose "[Étape 5] Exécution du script deploy-img.sh dans WSL Debian."
wsl -d Debian bash -lc $innerCmd
+57 -11
View File
@@ -2,7 +2,7 @@
# Script: deploy-img.sh
# Description: Pousse l'image Docker locale vers un registre HTTP (insecure), gère le versioning semver et la rétention
# Date: 2025-04-18
# Mise à jour: 2025-09-19 - Vérification stricte du daemon (insecure-registries), compatibilité Windows/Linux
# Mise à jour: 2026-09-28 - Lecture des identifiants depuis .registry.env, échec rapide si le registre est protégé
set -euo pipefail
@@ -29,6 +29,16 @@ JQ_BIN="${JQ_BIN:-jq}" # possibilité de surcharger via env
# Headers pour obtenir le digest (manifest v2)
ACCEPT_MANIFEST='application/vnd.docker.distribution.manifest.v2+json'
# Auth du registre. Ordre de priorité :
# 1. variables déjà exports (DOCKER_USERNAME / DOCKER_PASSWORD)
# 2. fichier .registry.env à côté du script (non versionné, cf. .gitignore)
# Le fichier permet à deploy-img.ps1 (qui exécute ce script dans WSL) de trouver
# les identifiants : WSL n'hérite pas des variables d'environnement de Windows.
# (Ce bloc est exécuté plus bas, après la définition de info/warn/error.)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REGISTRY_ENV_FILE="${REGISTRY_ENV_FILE:-${SCRIPT_DIR}/.registry.env}"
AUTH_ARGS=()
#####################################
# UTILITAIRES #
#####################################
@@ -55,6 +65,23 @@ require_cmd() {
command -v "$1" >/dev/null 2>&1 || { error "Commande requise manquante: $1"; exit 1; }
}
# Chargement des identifiants (voir commentaire en section CONFIG)
if [[ -f "${REGISTRY_ENV_FILE}" ]]; then
info "Lecture des identifiants depuis ${REGISTRY_ENV_FILE}"
set -a
# shellcheck disable=SC1090
source "${REGISTRY_ENV_FILE}"
set +a
fi
if [[ -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_PASSWORD:-}" ]]; then
AUTH_ARGS=(-u "${DOCKER_USERNAME}:${DOCKER_PASSWORD}")
else
warn "Aucun identifiant registre (DOCKER_USERNAME / DOCKER_PASSWORD)."
warn "Créez ${REGISTRY_ENV_FILE} avec :"
printf '%s\n' " DOCKER_USERNAME=votre_utilisateur" " DOCKER_PASSWORD=votre_mot_de_passe"
fi
#####################################
# PRÉ-VÉRIFICATIONS #
#####################################
@@ -96,20 +123,39 @@ if ! docker info 2>/dev/null \
exit 1
fi
# Ping HTTP direct du registre (API v2)
# Ping HTTP direct du registre (API v2).
# Un registre protégé répond 401 : cela prouve qu'il EST joignable, donc on
# accepte 200 comme 401 (et seulement eux). `-f` seul faisait échouer le script
# sur un registre authentifié.
info "Vérification HTTP directe du registre (http://${REGISTRY}/v2/)…"
if ! ${CURL} "http://${REGISTRY}/v2/" >/dev/null 2>&1; then
error "Impossible de joindre http://${REGISTRY}/v2/. Le registre écoute-t-il bien en HTTP sur :${REGISTRY_PORT} ?"
HTTP_CODE="$(curl -s -o /dev/null -w '%{http_code}' ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} \
--connect-timeout 10 -m 20 "http://${REGISTRY}/v2/" 2>/dev/null || echo "000")"
if [[ "${HTTP_CODE}" != "200" && "${HTTP_CODE}" != "401" ]]; then
error "Impossible de joindre http://${REGISTRY}/v2/ (code HTTP: ${HTTP_CODE})."
error "Le registre écoute-t-il bien en HTTP sur :${REGISTRY_PORT} ?"
exit 1
fi
# Un 401 sur un registre protégé SANS identifiants est bloquant : continuer
# produirait un push qui échoue en fin de course ("push access denied") après
# avoir déjà tagué l'image. On s'arrête ici avec un message actionnable.
if [[ "${HTTP_CODE}" == "401" && ${#AUTH_ARGS[@]} -eq 0 ]]; then
error "Le registre ${REGISTRY} exige une authentification et aucun identifiant n'est fourni."
error "Créez ${REGISTRY_ENV_FILE} :"
printf '%s\n' " DOCKER_USERNAME=votre_utilisateur" " DOCKER_PASSWORD=votre_mot_de_passe"
exit 1
fi
[[ "${HTTP_CODE}" == "401" ]] && info "Registre protégé (401) : authentification avec ${DOCKER_USERNAME}."
# Auth facultative via env (DOCKER_USERNAME / DOCKER_PASSWORD)
if [[ -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_PASSWORD:-}" ]]; then
# Auth auprès du daemon (utile pour le push)
if [[ ${#AUTH_ARGS[@]} -gt 0 ]]; then
info "Authentification au registre (docker login)…"
# NB: docker login respecte le mode HTTP si daemon configuré en insecure-registries
echo "${DOCKER_PASSWORD}" | docker login "${REGISTRY}" --username "${DOCKER_USERNAME}" --password-stdin >/dev/null
if ! echo "${DOCKER_PASSWORD}" | docker login "${REGISTRY}" --username "${DOCKER_USERNAME}" --password-stdin >/dev/null 2>&1; then
error "docker login a échoué sur ${REGISTRY}. Vérifiez DOCKER_USERNAME / DOCKER_PASSWORD."
exit 1
fi
else
info "Aucun identifiant Docker fourni (DOCKER_USERNAME/DOCKER_PASSWORD). Tentative sans authentification."
info "Aucun identifiant : le registre est ouvert, push sans authentification."
fi
mkdir -p "${TEMP_DIR}"
@@ -121,7 +167,7 @@ get_all_tags() {
# Renvoie la liste des tags (un par ligne) ou rien si vide/erreur
# Utilise jq si dispo, sinon un parseur simple.
local tags_json
if ! tags_json="$(${CURL} "http://${REGISTRY}/v2/${IMAGE_NAME}/tags/list" 2>/dev/null)"; then
if ! tags_json="$(${CURL} ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} "http://${REGISTRY}/v2/${IMAGE_NAME}/tags/list" 2>/dev/null)"; then
return 0
fi
@@ -190,7 +236,7 @@ delete_by_tag() {
local tag="$1"
local digest
# On demande le digest via HEAD + Accept manifest v2
digest="$(curl -fsSI -H "Accept: ${ACCEPT_MANIFEST}" "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${tag}" \
digest="$(curl -fsSI ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} -H "Accept: ${ACCEPT_MANIFEST}" "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${tag}" \
| tr -d '\r' \
| awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}' \
| tail -n1)"
@@ -201,7 +247,7 @@ delete_by_tag() {
info "Suppression manifest ${tag} (digest: ${digest})…"
# La delete API ne renvoie rien en cas de succès (204)
if ! curl -fsS -X DELETE "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${digest}" >/dev/null; then
if ! curl -fsS ${AUTH_ARGS[@]+"${AUTH_ARGS[@]}"} -X DELETE "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${digest}" >/dev/null; then
warn "Échec suppression manifest pour ${tag} (digest: ${digest})."
fi
}
@@ -39,18 +39,7 @@
»
</button>
<!-- Replier la bande -->
<button type="button" (click)="toggleCollapsed()" [attr.aria-expanded]="true" aria-controls="themes-bar"
[title]="'themes.hide' | t : 'Masquer la bande des thèmes'"
class="absolute right-12 top-1/2 z-10 hidden h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full shadow transition focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 md:flex"
[ngClass]="{
'bg-white/90 border border-slate-300 text-slate-700 hover:bg-slate-100': isLightTheme(),
'bg-slate-800/90 border border-slate-700 text-slate-200 hover:bg-slate-700': isDarkTheme() || isBlackTheme() || isBlueTheme()
}"
aria-label="Masquer la bande des thèmes">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd" /></svg>
</button>
<!-- Replier la bande : action disponible uniquement dans le header -->
<div class="w-full overflow-x-auto no-scrollbar" #scroll (wheel)="onWheel($event)">
<ul role="tablist" aria-label="Themes" class="flex gap-2 px-12 py-2 min-w-max items-center">
<li *ngFor="let t of displayedThemes(); let i = index" class="shrink-0">
@@ -72,12 +61,6 @@
<span>{{ themesSvc.i18nLabel(t) }}</span>
</button>
</li>
<li class="shrink-0 md:hidden">
<button type="button" (click)="toggleCollapsed()"
class="px-3 py-1.5 rounded-full text-xs font-semibold text-slate-400 border border-slate-700 whitespace-nowrap">
{{ 'themes.hide' | t : 'Masquer' }}
</button>
</li>
</ul>
</div>
</nav>