first commit

This commit is contained in:
2025-09-27 18:13:02 -04:00
commit 6e1b5407b7
253 changed files with 289900 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
# syntax=docker/dockerfile:1
# -------------------- Build stage --------------------
FROM node:20-bullseye AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci
# Copy sources
COPY . .
# Build Angular app (outputs to ./dist)
RUN npx ng build --configuration=production
# Prune dev dependencies to keep only production deps for runtime copy
RUN npm prune --omit=dev
# -------------------- Runtime stage --------------------
FROM node:20-bullseye-slim AS runner
ENV NODE_ENV=production
WORKDIR /app
# Install curl for healthcheck
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Create required runtime directories
RUN mkdir -p /app/db /app/tmp/downloads /app/vault
# Copy runtime server and built frontend
COPY --from=builder /app/server ./server
COPY --from=builder /app/dist ./dist
# Copy production dependencies from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
# Copy config and assets
COPY --from=builder /app/assets ./assets
# Copy database schema
COPY --from=builder /app/db/schema.sql ./db/schema.sql
# Create volume mount points for Obsidian vault
VOLUME ["/app/vault"]
# Expose API/web port
EXPOSE 4000
# Healthcheck against API
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD curl -fsS http://localhost:4000/api/health || exit 1
# Start the API server (serves the Angular build from ./dist)
CMD ["node", "./server/index.mjs"]
+72
View File
@@ -0,0 +1,72 @@
<#
.SYNOPSIS
Construit l'image Docker avec toutes les dépendances requises via PowerShell et WSL (Debian).
.DESCRIPTION
Ce script prépare les dépendances et construit l'image Docker sous WSL (Debian).
.PARAMETRES
-full : Si présent, effectue une construction complète de l'image Docker (équivalent à --no-cache).
.EXEMPLE
.\build-img.ps1 # Construction rapide (avec cache)
.\build-img.ps1 -full # Construction complète sans cache
.RETOURNE
Affiche le succès ou les erreurs pendant la construction de l'image Docker.
#>
param(
[switch]$full
)
$ErrorActionPreference = 'Stop'
try {
# Determine paths
$scriptDir = $PSScriptRoot
$projectRoot = (Get-Item (Join-Path $scriptDir '..')).FullName
# Convert Windows path to WSL path (manual, no wslpath)
$wslProjectRoot = $projectRoot -replace '^([A-Za-z]):\\','/mnt/$1/' -replace '\\','/'
$wslProjectRoot = $wslProjectRoot.ToLower()
# Check if Docker is available in WSL Debian
Write-Host "Vérification de Docker dans WSL Debian..." -ForegroundColor Cyan
$dockerCheck = wsl -d Debian bash -c "docker --version" 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Error "Docker n'est pas disponible dans WSL Debian : $dockerCheck"
exit 1
}
Write-Host "Docker détecté : $dockerCheck" -ForegroundColor Green
# Check if the Dockerfile exists
$dockerfilePath = Join-Path $scriptDir "Dockerfile"
if (-not (Test-Path $dockerfilePath)) {
Write-Error "Le fichier Dockerfile n'existe pas dans $scriptDir"
exit 1
}
# Compose build command
$noCache = if ($full) { '--no-cache' } else { '' }
$innerCmd = "cd '$wslProjectRoot' && docker build $noCache -t obsiviewer-angular:latest -f docker/Dockerfile ."
# Run build inside WSL Debian to use Linux Docker daemon
Write-Host "Construction de l'image Docker obsiviewer-angular:latest..." -ForegroundColor Cyan
$buildResult = wsl -d Debian bash -lc $innerCmd 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Error "Erreur lors de la construction de l'image Docker : $buildResult"
exit 1
}
# Verify the image was built successfully
$verifyCmd = "docker image inspect obsiviewer-angular:latest"
Write-Host "Vérification de l'image construite..." -ForegroundColor Cyan
$verifyResult = wsl -d Debian bash -lc $verifyCmd 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Error "L'image Docker n'a pas été construite correctement : $verifyResult"
exit 1
}
Write-Host "Image Docker obsiviewer-angular:latest construite avec succès via WSL Debian." -ForegroundColor Green
}
catch {
Write-Error "Erreur lors de la construction de l'image Docker : $_"
exit 1
}
+18
View File
@@ -0,0 +1,18 @@
events {}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
}
+46
View File
@@ -0,0 +1,46 @@
<#
.SYNOPSIS
Exécute le script deploy-img.sh dans WSL Debian.
.DESCRIPTION
Ce script PowerShell lance le script Bash deploy-img.sh dans le répertoire courant sous la distribution WSL Debian.
Le script déploie l'image Docker locale vers le registre distant et gère les versions.
.PARAMETRES
Aucun paramètre requis actuellement.
.EXEMPLE
.\deploy-img.ps1
.RETOURNE
Affiche le succès ou les erreurs lors du déploiement de l'image Docker.
#>
param()
$ErrorActionPreference = 'Stop'
try {
Write-Verbose "[Étape 1] Détermination du répertoire du script."
$scriptDir = $PSScriptRoot
Write-Verbose "[Étape 2] Conversion du chemin Windows vers WSL (manuel, sans wslpath)."
# Remplacement manuel du préfixe du chemin Windows par le préfixe WSL
$wslScriptDir = $scriptDir -replace '^([A-Za-z]):\\', '/mnt/$1/' -replace '\\','/'
$wslScriptDir = $wslScriptDir.ToLower()
Write-Verbose "[Étape 3] Vérification des permissions du script bash."
wsl -d Debian -- chmod +x "$wslScriptDir/deploy-img.sh"
Write-Verbose "[Étape 4] Construction de la commande Bash à exécuter."
$innerCmd = "cd '$wslScriptDir' && ./deploy-img.sh"
Write-Verbose "[Étape 5] Exécution du script deploy-img.sh dans WSL Debian."
wsl -d Debian bash -lc $innerCmd
if ($LASTEXITCODE -ne 0) {
throw "Le script de déploiement a échoué avec le code d'erreur: $LASTEXITCODE"
}
Write-Host "Déploiement de l'image Docker obsiviewer-angular:latest terminé avec succès." -ForegroundColor Green
}
catch {
Write-Error "Erreur lors de l'exécution du déploiement de l'image Docker : $_"
exit 1
}
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env bash
# 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
set -euo pipefail
#####################################
# CONFIG UTILISATEUR #
#####################################
IMAGE_NAME="obsiviewer-angular"
REGISTRY_HOST="docker-registry.dev.home"
REGISTRY_PORT="5000"
MAX_VERSIONS=5 # nombre de versions (semver) à conserver
LOCAL_TAG="latest" # tag local source
PUSH_LATEST="yes" # yes/no : pousser aussi :latest
#####################################
# DÉDUCTIONS & CONSTANTES #
#####################################
REGISTRY="${REGISTRY_HOST}:${REGISTRY_PORT}"
REMOTE_REPO="${REGISTRY}/${IMAGE_NAME}"
LOCAL_IMAGE="${IMAGE_NAME}:${LOCAL_TAG}"
TEMP_DIR="/tmp/docker-deploy-$(date +%s)"
CURL="curl -fsSL"
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'
#####################################
# UTILITAIRES #
#####################################
info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[AVERT]\033[0m $*"; }
error() { echo -e "\033[1;31m[ERREUR]\033[0m $*" >&2; }
success() { echo -e "\033[1;32m[SUCCÈS]\033[0m $*"; }
cleanup() {
set +e
[[ -d "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"
}
trap cleanup EXIT
is_windows_shell() {
# Git Bash / MSYS / CYGWIN ou variable OS=Windows_NT
if [[ "${OS:-}" == "Windows_NT" ]] || uname -s | grep -qiE 'mingw|msys|cygwin'; then
return 0
fi
return 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || { error "Commande requise manquante: $1"; exit 1; }
}
#####################################
# PRÉ-VÉRIFICATIONS #
#####################################
require_cmd docker
require_cmd curl
require_cmd sed
require_cmd grep
require_cmd awk
require_cmd sort
# Vérifier l'image locale
info "Vérification de l'image locale '${LOCAL_IMAGE}'…"
if ! docker image inspect "${LOCAL_IMAGE}" >/dev/null 2>&1; then
error "L'image locale ${LOCAL_IMAGE} n'existe pas. Construis-la d'abord (docker build …)."
exit 1
fi
# Vérifier config daemon: insecure-registries
info "Vérification de la config du daemon Docker (insecure-registries)…"
if ! docker info >/dev/null 2>&1; then
error "Impossible de contacter le daemon Docker. Est-il démarré ?"
exit 1
fi
if ! docker info 2>/dev/null \
| tr -d '\r' \
| awk '/Insecure Registries:/,/^$/ {print}' \
| grep -q -E "(^|[[:space:]])${REGISTRY_HOST}:${REGISTRY_PORT}([[:space:]]|$)"; then
error "Le daemon Docker n'a PAS '${REGISTRY}' dans insecure-registries."
if is_windows_shell; then
info "Sous Windows (Docker Desktop) : Settings → Docker Engine, ajoute dans le JSON :"
printf '%s\n' ' "insecure-registries": ["'"${REGISTRY}"'"]'
info "Puis clique 'Apply & Restart' et relance ce script."
else
info "Sous Linux : édite /etc/docker/daemon.json et ajoute par ex. :"
printf '%s\n' '{ "insecure-registries": ["'"${REGISTRY}"'"] }'
info "Puis : sudo systemctl restart docker"
fi
exit 1
fi
# Ping HTTP direct du registre (API v2)
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} ?"
exit 1
fi
# Auth facultative via env (DOCKER_USERNAME / DOCKER_PASSWORD)
if [[ -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_PASSWORD:-}" ]]; 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
else
info "Aucun identifiant Docker fourni (DOCKER_USERNAME/DOCKER_PASSWORD). Tentative sans authentification."
fi
mkdir -p "${TEMP_DIR}"
#####################################
# RÉCUPÉRATION DES TAGS #
#####################################
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
return 0
fi
if command -v "${JQ_BIN}" >/dev/null 2>&1; then
echo "${tags_json}" | ${JQ_BIN} -r '.tags[]?' 2>/dev/null || true
else
# fallback très simple (non robuste à tous les cas, mais suffisant ici)
echo "${tags_json}" \
| tr -d '\n' \
| sed -n 's/.*"tags":[[]\([^]]*\)[]].*/\1/p' \
| tr -d '"' \
| tr ',' '\n' \
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
fi
}
filter_semver() {
# Garde uniquement X.Y.Z (numériques)
grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' || true
}
increment_patch() {
# Lit un tag semver X.Y.Z et augmente Z
local tag="$1"
local major minor patch
IFS='.' read -r major minor patch <<< "${tag}"
echo "${major}.${minor}.$((patch+1))"
}
info "Récupération des tags existants…"
ALL_TAGS="$(get_all_tags || true)"
SEMVER_TAGS="$(printf '%s\n' "${ALL_TAGS}" | filter_semver | sort -V || true)"
if [[ -z "${SEMVER_TAGS}" ]]; then
NEW_TAG="1.0.0"
else
LATEST_TAG="$(printf '%s\n' "${SEMVER_TAGS}" | tail -n1)"
NEW_TAG="$(increment_patch "${LATEST_TAG}")"
fi
info "Tag retenu pour cette release : ${NEW_TAG}"
#####################################
# TAG + PUSH (HTTP via daemon)
#####################################
info "Taggage local → ${REMOTE_REPO}:${NEW_TAG}"
docker tag "${LOCAL_IMAGE}" "${REMOTE_REPO}:${NEW_TAG}"
if [[ "${PUSH_LATEST}" == "yes" ]]; then
info "Taggage local → ${REMOTE_REPO}:latest"
docker tag "${LOCAL_IMAGE}" "${REMOTE_REPO}:latest"
fi
info "Push de ${REMOTE_REPO}:${NEW_TAG} (HTTP via insecure-registries)…"
docker push --disable-content-trust "${REMOTE_REPO}:${NEW_TAG}"
if [[ "${PUSH_LATEST}" == "yes" ]]; then
info "Push de ${REMOTE_REPO}:latest…"
docker push --disable-content-trust "${REMOTE_REPO}:latest"
fi
#####################################
# RÉTENTION: SUPPRIMER ANCIENS #
#####################################
delete_by_tag() {
# Supprime un manifest par son tag en récupérant le digest via HEAD
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}" \
| tr -d '\r' \
| awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}' \
| tail -n1)"
if [[ -z "${digest}" ]]; then
warn "Digest introuvable pour ${tag} (manifest v2 absent ?). Skip."
return 0
fi
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
warn "Échec suppression manifest pour ${tag} (digest: ${digest})."
fi
}
# Mettre à jour la liste après push
ALL_TAGS="$(get_all_tags || true)"
SEMVER_TAGS="$(printf '%s\n' "${ALL_TAGS}" | filter_semver | sort -V || true)"
if [[ -n "${SEMVER_TAGS}" ]]; then
COUNT="$(printf '%s\n' "${SEMVER_TAGS}" | wc -l | awk '{print $1}')"
if (( COUNT > MAX_VERSIONS )); then
TO_DELETE_COUNT=$(( COUNT - MAX_VERSIONS ))
# On supprime les plus anciennes
OLDEST="$(printf '%s\n' "${SEMVER_TAGS}" | head -n "${TO_DELETE_COUNT}")"
while IFS= read -r old_tag; do
[[ -z "${old_tag}" ]] && continue
# Ne jamais supprimer le tag que l'on vient de pousser (sécurité)
if [[ "${old_tag}" == "${NEW_TAG}" ]]; then
continue
fi
delete_by_tag "${old_tag}"
done <<< "${OLDEST}"
fi
fi
#####################################
# NETTOYAGE LOCAL OPTIONNEL #
#####################################
info "Nettoyage des tags locaux temporaires…"
set +e
docker rmi "${REMOTE_REPO}:${NEW_TAG}" >/dev/null 2>&1
[[ "${PUSH_LATEST}" == "yes" ]] && docker rmi "${REMOTE_REPO}:latest" >/dev/null 2>&1
set -e
success "Déploiement terminé. Version publiée : ${NEW_TAG}"
exit 0
View File
+30
View File
@@ -0,0 +1,30 @@
#!/bin/sh
# Create output dir if missing
mkdir -p /usr/share/nginx/html/assets
# Create a JavaScript config file with environment variables
cat > /usr/share/nginx/html/assets/config.js << 'EOL'
// Configuration générée automatiquement à partir des variables d'environnement
(function(window) {
// API Keys and configuration from environment variables
window.GEMINI_API_KEY = '${GEMINI_API_KEY}';
window.YOUTUBE_API_KEY = '${YOUTUBE_API_KEY}';
window.YOUTUBE_API_KEYS = '${YOUTUBE_API_KEYS}';
window.VIMEO_ACCESS_TOKEN = '${VIMEO_ACCESS_TOKEN}';
window.TWITCH_CLIENT_ID = '${TWITCH_CLIENT_ID}';
window.TWITCH_CLIENT_SECRET = '${TWITCH_CLIENT_SECRET}';
window.YT_CACHE_TTL_MS = '${YT_CACHE_TTL_MS}';
// Base menu configuration
window.BASE_MENU_CONFIG_ASSETS = '${BASE_MENU_CONFIG_ASSETS:-}';
})(window);
EOL
# Set proper permissions
chmod 644 /usr/share/nginx/html/assets/config.js
# Keep the original env.json for backward compatibility
if [ ! -f /usr/share/nginx/html/assets/env.json ]; then
printf '{\n "BASE_MENU_CONFIG_ASSETS": ""\n}\n' > /usr/share/nginx/html/assets/env.json
fi