From 222b026ac55ed9443e61b9aeef22badc7bdb4e4c Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Mon, 22 Dec 2025 19:23:15 +0100 Subject: [PATCH] Add docker image building --- .dockerignore | 70 ++++++++ DOCKER.md | 302 ++++++++++++++++++++++++++++++++ Dockerfile | 108 ++++++++++++ docker-build.sh | 135 ++++++++++++++ docker-compose.yml | 39 +++++ package-lock.json | 6 + pmoapp/webapp/package-lock.json | 19 ++ pmoapp/webapp/package.json | 1 + version.txt | 1 + 9 files changed, 681 insertions(+) create mode 100644 .dockerignore create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100755 docker-build.sh create mode 100644 docker-compose.yml create mode 100644 package-lock.json create mode 100644 version.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..86e57e4c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,70 @@ +# Build artifacts +target/ +**/target/ + +# Webapp build artifacts and dependencies +pmoapp/webapp/node_modules/ +pmoapp/webapp/dist/ +**/node_modules/ + +# Git +.git/ +.gitignore +.jj/ + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# macOS +.DS_Store + +# Cache directories +cache/ +.pmomusic/ + +# Log files +*.log + +# Documentation +doc/ +*.md +!Readme.md + +# Test files +test_upnp/ +examples/ + +# Temporary files +*.tmp +*.temp +*.pcap + +# Database files +db/ + +# Old code and backups +old_code/ +*.txt +!Cargo.lock + +# Development tools +tools/ +gupnp-tools/ + +# Build scripts (we have them in the Dockerfile) +Makefile +setup-deps.sh +setup-env.sh + +# SVG and other assets not needed for runtime +*.svg + +# Audio test files +*.ogg +*.flac +*.wav +*.mp3 diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 00000000..07079d0b --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,302 @@ +# Docker Deployment Guide for PMOMusic + +Ce guide explique comment construire et déployer PMOMusic avec Docker. + +## Architecture + +Le Dockerfile utilise une approche multi-stage pour créer une image minimale : + +1. **Stage 1 (webapp-builder)** : Compile l'application Vue.js avec Node.js +2. **Stage 2 (rust-builder)** : Compile le binaire Rust avec toutes ses dépendances +3. **Stage 3 (runtime)** : Image finale minimale Debian Slim avec uniquement le binaire et les bibliothèques runtime + +### Avantages + +- **Binaire auto-contenu** : L'application web est embarquée dans le binaire Rust +- **Image minimale** : ~200-300MB (vs plusieurs GB pour les images de build) +- **Sécurité** : Exécution en tant qu'utilisateur non-root +- **Reproductibilité** : Build complet et déterministe + +## Build de l'image + +### Option 1 : Build manuel avec Docker + +```bash +# Build l'image +docker build -t pmomusic:latest . + +# Le build prend environ 10-15 minutes selon votre machine +``` + +### Option 2 : Build avec docker-compose + +```bash +# Build et démarre le conteneur +docker-compose up --build + +# Ou juste build +docker-compose build +``` + +### Build optimisé avec cache + +Pour accélérer les builds successifs, Docker réutilise les couches en cache : + +```bash +# Build avec cache +docker build -t pmomusic:latest . + +# Build sans cache (force rebuild complet) +docker build --no-cache -t pmomusic:latest . +``` + +## Exécution du conteneur + +### Option 1 : Avec docker-compose (recommandé) + +```bash +# Démarrer en arrière-plan +docker-compose up -d + +# Voir les logs +docker-compose logs -f + +# Arrêter +docker-compose down + +# Redémarrer +docker-compose restart +``` + +### Option 2 : Avec docker run + +```bash +# Run en mode interactif +docker run -it --rm \ + --name pmomusic \ + --network host \ + -v $(pwd)/config:/home/pmomusic/.pmomusic \ + -v $(pwd)/cache:/home/pmomusic/cache \ + pmomusic:latest + +# Run en mode détaché +docker run -d \ + --name pmomusic \ + --network host \ + --restart unless-stopped \ + -v $(pwd)/config:/home/pmomusic/.pmomusic \ + -v $(pwd)/cache:/home/pmomusic/cache \ + pmomusic:latest +``` + +## Configuration + +### Ports + +Par défaut, PMOMusic écoute sur le port **8080**. Vous pouvez modifier cela : + +- Dans `docker-compose.yml` : modifier la section `ports` +- Avec `docker run` : utiliser `-p 8080:8080` + +### Volumes + +Deux volumes sont recommandés pour la persistance : + +- **Configuration** : `/home/pmomusic/.pmomusic` - Fichiers de configuration +- **Cache** : `/home/pmomusic/cache` - Cache audio et métadonnées + +### Variables d'environnement + +Configurable via `docker-compose.yml` ou `-e` avec `docker run` : + +```bash +# Niveau de logs Rust +RUST_LOG=debug + +# Autres variables (selon votre configuration) +# ... +``` + +### Réseau + +Pour UPnP/DLNA, utilisez **network_mode: host** pour permettre : +- La découverte multicast +- La communication avec les devices UPnP sur le réseau local + +**Note** : Le mode `host` ne fonctionne que sur Linux. Sur macOS/Windows avec Docker Desktop, utilisez le mapping de ports standard. + +## Gestion de l'image + +### Taille de l'image + +```bash +# Voir la taille de l'image +docker images pmomusic:latest + +# Résultat attendu : ~200-300MB +``` + +### Nettoyage + +```bash +# Supprimer l'image +docker rmi pmomusic:latest + +# Nettoyer les images de build intermédiaires +docker builder prune + +# Nettoyer tous les caches Docker (libère beaucoup d'espace) +docker system prune -a +``` + +## Build multi-plateforme + +Pour builder pour différentes architectures (ARM64, AMD64) : + +```bash +# Créer un builder multi-plateforme +docker buildx create --name multiarch --use + +# Build pour AMD64 et ARM64 +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t pmomusic:latest \ + --push \ + . + +# Note : nécessite un registry Docker (Docker Hub, GHCR, etc.) +``` + +## Déploiement en production + +### 1. Avec docker-compose (simple) + +```bash +# Sur le serveur de production +git clone +cd pmomusic +docker-compose up -d +``` + +### 2. Avec un registry Docker (recommandé) + +```bash +# Sur votre machine de dev +docker build -t yourregistry.com/pmomusic:v1.0.0 . +docker push yourregistry.com/pmomusic:v1.0.0 + +# Sur le serveur de production +docker pull yourregistry.com/pmomusic:v1.0.0 +docker run -d ... yourregistry.com/pmomusic:v1.0.0 +``` + +### 3. Avec un orchestrateur (Kubernetes, Docker Swarm) + +Créer un fichier de déploiement approprié selon votre orchestrateur. + +## Debugging + +### Logs du conteneur + +```bash +# Logs en temps réel +docker logs -f pmomusic + +# Logs avec docker-compose +docker-compose logs -f +``` + +### Entrer dans le conteneur + +```bash +# Shell interactif (bash n'est pas disponible, utiliser sh) +docker exec -it pmomusic sh + +# Vérifier les processus +docker exec -it pmomusic ps aux + +# Vérifier les fichiers +docker exec -it pmomusic ls -la /home/pmomusic +``` + +### Health check + +Le conteneur inclut un health check. Vérifier l'état : + +```bash +# Voir l'état de santé +docker inspect --format='{{.State.Health.Status}}' pmomusic +``` + +## Troubleshooting + +### Le build échoue + +1. **Erreur de dépendances npm** : + - Vérifier que `pmoapp/webapp/package.json` est correct + - Essayer `docker build --no-cache` + +2. **Erreur de compilation Rust** : + - Vérifier que tous les fichiers Cargo.toml sont présents + - Vérifier les dépendances système (libsoxr, libasound2) + +3. **Out of memory** : + - Augmenter la mémoire allouée à Docker Desktop (settings) + - Utiliser `--memory` pour limiter la mémoire du build + +### Le conteneur ne démarre pas + +1. **Port déjà utilisé** : + ```bash + # Vérifier quel processus utilise le port 8080 + sudo lsof -i :8080 + ``` + +2. **Permissions** : + - Vérifier les permissions des volumes montés + - Le conteneur s'exécute en tant qu'utilisateur `pmomusic` (UID 1000) + +3. **Configuration manquante** : + - Créer les répertoires de configuration avant de démarrer : + ```bash + mkdir -p config cache + ``` + +### UPnP ne fonctionne pas + +1. **Network mode** : + - Sur Linux : utiliser `network_mode: host` + - Sur macOS/Windows : UPnP peut ne pas fonctionner correctement avec Docker Desktop + +2. **Firewall** : + - Vérifier que les ports UPnP ne sont pas bloqués + - Autoriser le multicast sur le réseau + +## Performance + +### Optimisations du build + +1. **Build cache** : Docker réutilise les couches en cache +2. **Multi-stage build** : Réduit la taille de l'image finale +3. **Strip des symboles** : Le binaire est strippé pour réduire sa taille + +### Optimisations runtime + +1. **Resource limits** : Définir des limites CPU/mémoire dans docker-compose.yml +2. **Volumes** : Utiliser des volumes pour les données persistantes +3. **Logs** : Configurer la rotation des logs Docker + +## Sécurité + +- ✅ Exécution en tant qu'utilisateur non-root +- ✅ Image minimale (surface d'attaque réduite) +- ✅ Pas de secrets dans l'image +- ✅ Health checks activés +- ✅ Certificats CA inclus pour HTTPS + +## Références + +- [Dockerfile](./Dockerfile) +- [docker-compose.yml](./docker-compose.yml) +- [.dockerignore](./.dockerignore) +- [Documentation Rust](./Readme.md) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5ad73435 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,108 @@ +# =================================== +# Stage 1: Build Vue.js webapp +# =================================== +FROM node:22-alpine AS webapp-builder + +WORKDIR /webapp + +# Copy webapp package files +COPY pmoapp/webapp/package*.json ./ + +# Install dependencies +RUN npm ci --production=false + +# Copy webapp source +COPY pmoapp/webapp/ ./ + +# Build the webapp +RUN npm run build + +# =================================== +# Stage 2: Build Rust binary +# =================================== +FROM rustlang/rust:nightly-bookworm AS rust-builder + +WORKDIR /build + +# Install system dependencies for building +RUN apt-get update && apt-get install -y \ + libsoxr-dev \ + libasound2-dev \ + pkg-config \ + cmake \ + && rm -rf /var/lib/apt/lists/* + +# Copy Cargo workspace files +COPY Cargo.toml Cargo.lock ./ + +# Copy all crates +COPY PMOMusic/ ./PMOMusic/ +COPY pmoupnp/ ./pmoupnp/ +COPY pmomediarenderer/ ./pmomediarenderer/ +COPY pmomediaserver/ ./pmomediaserver/ +COPY pmoconfig/ ./pmoconfig/ +COPY pmoutils/ ./pmoutils/ +COPY pmodidl/ ./pmodidl/ +COPY pmoserver/ ./pmoserver/ +COPY pmocache/ ./pmocache/ +COPY pmocovers/ ./pmocovers/ +COPY pmoaudiocache/ ./pmoaudiocache/ +COPY pmoaudio/ ./pmoaudio/ +COPY pmoqobuz/ ./pmoqobuz/ +COPY pmoparadise/ ./pmoparadise/ +COPY pmosource/ ./pmosource/ +COPY pmoplaylist/ ./pmoplaylist/ +COPY pmoflac/ ./pmoflac/ +COPY pmometadata/ ./pmometadata/ +COPY pmocontrol/ ./pmocontrol/ +COPY pmoaudio-ext/ ./pmoaudio-ext/ +COPY pmoapp/ ./pmoapp/ + +# Copy the webapp dist from previous stage +COPY --from=webapp-builder /webapp/dist ./pmoapp/webapp/dist/ + +# Build the Rust binary in release mode +RUN cargo build --release --bin PMOMusic + +# Strip debug symbols to reduce binary size +RUN strip /build/target/release/PMOMusic + +# =================================== +# Stage 3: Minimal runtime image +# =================================== +FROM debian:bookworm-slim + +# Install only runtime dependencies +RUN apt-get update && apt-get install -y \ + libsoxr0 \ + libasound2 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Create a non-root user +RUN useradd -m -u 1000 pmomusic + +# Copy the binary from builder +COPY --from=rust-builder /build/target/release/PMOMusic /usr/local/bin/PMOMusic + +# Set ownership +RUN chown pmomusic:pmomusic /usr/local/bin/PMOMusic + +# Switch to non-root user +USER pmomusic + +# Create directories for configuration and cache +RUN mkdir -p /home/pmomusic/.pmomusic + +# Set working directory +WORKDIR /home/pmomusic + +# Expose default port (adjust if needed) +EXPOSE 8080 + +# Health check (adjust the URL if needed) +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD ["/usr/local/bin/PMOMusic", "--help"] || exit 1 + +# Run the binary +ENTRYPOINT ["/usr/local/bin/PMOMusic"] diff --git a/docker-build.sh b/docker-build.sh new file mode 100755 index 00000000..0defdf19 --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# Script de build Docker pour PMOMusic +# Usage: ./docker-build.sh [OPTIONS] + +set -e + +# Couleurs pour l'affichage +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Configuration par défaut +IMAGE_NAME="pmomusic" +TAG="latest" +NO_CACHE=false +PUSH=true +REGISTRY="niepce.petite-maison-orange.fr/public" + +# Fonction d'aide +show_help() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -t, --tag TAG Tag de l'image (défaut: latest)" + echo " -r, --registry URL Registry Docker (ex: ghcr.io/user)" + echo " -n, --no-cache Build sans cache" + echo " -p, --push Push l'image vers le registry" + echo " -h, --help Affiche cette aide" + echo "" + echo "Exemples:" + echo " $0 # Build local avec tag 'latest'" + echo " $0 -t v1.0.0 # Build avec tag 'v1.0.0'" + echo " $0 -t v1.0.0 -r ghcr.io/user -p # Build et push vers GHCR" + echo " $0 -n # Build sans cache" + exit 0 +} + +# Parsing des arguments +while [[ $# -gt 0 ]]; do + case $1 in + -t|--tag) + TAG="$2" + shift 2 + ;; + -r|--registry) + REGISTRY="$2" + shift 2 + ;; + -n|--no-cache) + NO_CACHE=true + shift + ;; + -p|--push) + PUSH=true + shift + ;; + -h|--help) + show_help + ;; + *) + echo -e "${RED}Erreur: Option inconnue '$1'${NC}" + show_help + ;; + esac +done + +# Construire le nom complet de l'image +if [ -n "$REGISTRY" ]; then + FULL_IMAGE_NAME="$REGISTRY/$IMAGE_NAME:$TAG" +else + FULL_IMAGE_NAME="$IMAGE_NAME:$TAG" +fi + +# Afficher la configuration +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Build Docker PMOMusic${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo "Image: $FULL_IMAGE_NAME" +echo "No cache: $NO_CACHE" +echo "Push: $PUSH" +echo "" + +# Construire la commande Docker +DOCKER_CMD="docker build" + +if [ "$NO_CACHE" = true ]; then + DOCKER_CMD="$DOCKER_CMD --no-cache" +fi + +DOCKER_CMD="$DOCKER_CMD -t $FULL_IMAGE_NAME ." + +# Exécuter le build +echo -e "${YELLOW}→ Démarrage du build...${NC}" +echo "Commande: $DOCKER_CMD" +echo "" + +if eval "$DOCKER_CMD"; then + echo "" + echo -e "${GREEN}✓ Build réussi !${NC}" + + # Afficher la taille de l'image + IMAGE_SIZE=$(docker images "$FULL_IMAGE_NAME" --format "{{.Size}}") + echo "Taille de l'image: $IMAGE_SIZE" + + # Push si demandé + if [ "$PUSH" = true ]; then + echo "" + echo -e "${YELLOW}→ Push de l'image vers le registry...${NC}" + + if docker push "$FULL_IMAGE_NAME"; then + echo -e "${GREEN}✓ Image pushée avec succès !${NC}" + else + echo -e "${RED}✗ Erreur lors du push${NC}" + exit 1 + fi + fi + + echo "" + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}Build terminé avec succès !${NC}" + echo -e "${GREEN}========================================${NC}" + echo "" + echo "Pour lancer le conteneur:" + echo " docker run -it --rm --network host $FULL_IMAGE_NAME" + echo "" + echo "Ou avec docker-compose:" + echo " docker-compose up -d" + +else + echo "" + echo -e "${RED}✗ Erreur lors du build${NC}" + exit 1 +fi diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f0916a54 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +version: '3.8' + +services: + pmomusic: + build: + context: . + dockerfile: Dockerfile + image: pmomusic:latest + container_name: pmomusic + + # Port mapping (adjust to your needs) + ports: + - "8080:8080" + + # Volume for persistent configuration + volumes: + - ./config:/home/pmomusic/.pmomusic + - ./cache:/home/pmomusic/cache + + # Environment variables (adjust as needed) + environment: + - RUST_LOG=info + # Add other environment variables here + + # Network mode for UPnP/DLNA discovery (host mode for multicast) + network_mode: host + + # Restart policy + restart: unless-stopped + + # Resource limits (optional) + deploy: + resources: + limits: + cpus: '2.0' + memory: 1G + reservations: + cpus: '0.5' + memory: 256M diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..a13a2658 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "pmomusic", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/pmoapp/webapp/package-lock.json b/pmoapp/webapp/package-lock.json index 09afc506..a36a612c 100644 --- a/pmoapp/webapp/package-lock.json +++ b/pmoapp/webapp/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@types/dompurify": "^3.0.5", + "@types/node": "^22.0.0", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", "typescript": "~5.8.3", @@ -851,6 +852,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1502,6 +1514,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", diff --git a/pmoapp/webapp/package.json b/pmoapp/webapp/package.json index e71fba0b..a69bb749 100644 --- a/pmoapp/webapp/package.json +++ b/pmoapp/webapp/package.json @@ -18,6 +18,7 @@ "vue-virtual-scroller": "^2.0.0-beta.8" }, "devDependencies": { + "@types/node": "^22.0.0", "@types/dompurify": "^3.0.5", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", diff --git a/version.txt b/version.txt new file mode 100644 index 00000000..688abaae --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.10 \ No newline at end of file