From a2482ea268baea6e59d1d8c248d79080bd3e77a4 Mon Sep 17 00:00:00 2001 From: syoul Date: Fri, 20 Mar 2026 13:21:49 +0100 Subject: [PATCH] first commit clone prestashop-test --- .env.example | 13 +++ .gitignore | 6 ++ .woodpecker.yml | 222 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 73 +++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .woodpecker.yml create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..04de06d --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Domaine PrestaShop (utilise par Fabio pour le routage) +PS_DOMAIN=presta.syoul.fr + +# Dossier admin (renommer pour la securite) +PS_ADMIN_FOLDER=admin-secure + +# Compte administrateur PrestaShop +PRESTASHOP_ADMIN_EMAIL=admin@syoul.fr +PRESTASHOP_ADMIN_PASSWORD=changeme_fort + +# Base de donnees +DB_ROOT_PASSWORD=changeme_root +DB_PASSWORD=changeme_user diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4f43b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/docs-sbom +/docs-syoul +/docs-sonic +/plans +.env +/.reports/ \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..275fd48 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,222 @@ +when: + - branch: main + event: push + +steps: + + # Etape 1 : Validation syntaxique du docker-compose.yml + # Les vars CI (CI_REPO_OWNER, CI_COMMIT_BRANCH) sont injectees automatiquement par Woodpecker + - name: validate + image: docker:27-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + DB_PASSWORD: placeholder + DB_ROOT_PASSWORD: placeholder + PRESTASHOP_ADMIN_EMAIL: placeholder + PRESTASHOP_ADMIN_PASSWORD: placeholder + commands: + - | + export COMPOSE_PROJECT_NAME=$(printf '%s-%s-%s' "$CI_REPO_OWNER" "$CI_REPO_NAME" "$CI_COMMIT_BRANCH" | tr 'A-Z/' 'a-z-') + export PS_DOMAIN="validate.example.com" + export PS_ADMIN_FOLDER="admin-secure" + docker compose config --quiet + - echo "docker-compose.yml valide" + + # Etape 2 : Verifications de securite + - name: security-check + image: alpine:3.20 + commands: + - | + if [ -f .env ]; then + echo "ERREUR: .env ne doit pas etre commite dans le depot !" + exit 1 + fi + - 'grep -q "^\.env$" .gitignore || (echo "ERREUR: .env manquant dans .gitignore" && exit 1)' + - echo "Verifications de securite OK" + + # Etape 2b : Generation SBOM (Syft) — inventaire des composants des images Docker + # NOTE: volumes: et from_secret incompatibles dans le meme step — pas de secrets ici + - name: sbom-generate + image: alpine:3.20 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - apk add --no-cache curl + - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin latest + - mkdir -p .reports + - syft prestashop/prestashop:9.0.3-3.0-classic-8.3-apache -o cyclonedx-json --file .reports/sbom-prestashop.cyclonedx.json + - syft mariadb:10.11 -o cyclonedx-json --file .reports/sbom-mariadb.cyclonedx.json + - echo "SBOM generes $(ls .reports/sbom-*.json | wc -l) fichiers" + + # Etape 2c : Scan CVE (Trivy) depuis les SBOM Syft + # Cache /opt/trivy-cache evite ~200Mo de telechargement des DB CVE a chaque build + # Prerequis sur sonic : mkdir -p /opt/trivy-cache + - name: sbom-scan + image: aquasec/trivy:latest + volumes: + - /home/syoul/trivy-cache:/root/.cache/trivy + commands: + - trivy sbom --format json --output .reports/trivy-prestashop.json .reports/sbom-prestashop.cyclonedx.json + - trivy sbom --format json --output .reports/trivy-mariadb.json .reports/sbom-mariadb.cyclonedx.json + - echo "Scan CVE termine" + + # Etape 2d : Publication SBOM vers Dependency-Track (dtrack.syoul.fr) + # NOTE: from_secret et volumes: incompatibles — pas de volumes ici + - name: sbom-publish + image: alpine/curl:latest + environment: + DTRACK_TOKEN: + from_secret: dependency_track_token + commands: + - | + VERSION=$(date +%Y-%m-%d)-$(echo "$CI_COMMIT_SHA" | cut -c1-8) + HTTP=$(curl -s -o /tmp/dtrack-response.txt -w "%{http_code}" -X POST "https://dtrack.syoul.fr/api/v1/bom" \ + -H "X-Api-Key: $DTRACK_TOKEN" \ + -F "autoCreate=true" \ + -F "projectName=prestashop-test-app" \ + -F "projectVersion=$VERSION" \ + -F "bom=@.reports/sbom-prestashop.cyclonedx.json") + echo "HTTP $HTTP : $(cat /tmp/dtrack-response.txt)" + [ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1 + - | + VERSION=$(date +%Y-%m-%d)-$(echo "$CI_COMMIT_SHA" | cut -c1-8) + HTTP=$(curl -s -o /tmp/dtrack-response.txt -w "%{http_code}" -X POST "https://dtrack.syoul.fr/api/v1/bom" \ + -H "X-Api-Key: $DTRACK_TOKEN" \ + -F "autoCreate=true" \ + -F "projectName=prestashop-test-db" \ + -F "projectVersion=$VERSION" \ + -F "bom=@.reports/sbom-mariadb.cyclonedx.json") + echo "HTTP $HTTP : $(cat /tmp/dtrack-response.txt)" + [ "$HTTP" -ge 200 ] && [ "$HTTP" -lt 300 ] || exit 1 + + # Etape 3a : Ecriture du .env depuis les secrets + # NOTE: ne pas utiliser ${VAR} dans commands (bug Woodpecker next), utiliser env | grep + # NOTE: from_secret et volumes: incompatibles dans le meme step (bug Woodpecker next) + - name: write-env + image: alpine:3.20 + environment: + PS_DOMAIN: + from_secret: ps_domain + PS_ADMIN_FOLDER: + from_secret: ps_admin_folder + PRESTASHOP_ADMIN_EMAIL: + from_secret: prestashop_admin_email + PRESTASHOP_ADMIN_PASSWORD: + from_secret: prestashop_admin_password + DB_ROOT_PASSWORD: + from_secret: db_root_password + DB_PASSWORD: + from_secret: db_password + commands: + - env | grep -E "^(PS_DOMAIN|PS_ADMIN_FOLDER|PRESTASHOP_ADMIN_EMAIL|PRESTASHOP_ADMIN_PASSWORD|DB_ROOT_PASSWORD|DB_PASSWORD)=" > .env.deploy + # COMPOSE_PROJECT_NAME : convention user-project-branch, genere depuis les vars CI + - OWNER=$(echo "$CI_REPO_OWNER" | tr 'A-Z' 'a-z') && REPO=$(echo "$CI_REPO_NAME" | tr 'A-Z' 'a-z') && BRANCH=$(echo "$CI_COMMIT_BRANCH" | tr 'A-Z/' 'a-z-') && echo "COMPOSE_PROJECT_NAME=$OWNER-$REPO-$BRANCH" >> .env.deploy + - echo "Fichier .env.deploy cree ($(wc -c < .env.deploy) octets)" + + # Etape 3b : Deploiement sur sonic via Docker socket + # Modele pipeline sonic : deploy Docker Compose + cert TLS (acme.sh) + # Registrator enregistre automatiquement le container dans Consul via les labels SERVICE_* + # et publie les routes dans Fabio sans intervention manuelle + - name: deploy + image: docker:27-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /opt/prestashop:/opt/prestashop + commands: + - cp .env.deploy /opt/prestashop/.env + - chmod 600 /opt/prestashop/.env + - cp docker-compose.yml /opt/prestashop/docker-compose.yml + - cd /opt/prestashop && docker compose pull + - cd /opt/prestashop && docker compose up -d --remove-orphans + - cd /opt/prestashop && docker compose ps + - | + DOMAIN=$(grep '^PS_DOMAIN=' /opt/prestashop/.env | cut -d= -f2) + + # --- Certificat TLS (acme.sh via sonic-acme-1) --- + # acme.sh est idempotent : skip si cert valide, renouvelle si proche expiration + # Exit 0 = emis/renouvele, exit 2 = skip (domaine inchange), autres = erreur + # --home /etc/acme.sh = volume persistant sonic_acme (sinon /root/.acme.sh non persiste) + # || ACME_EXIT=$? capture le code sans declencher set -e (contrairement a ; ACME_EXIT=$?) + ACME_EXIT=0 + docker exec sonic-acme-1 /app/acme.sh \ + --home /etc/acme.sh \ + --issue -d "$DOMAIN" \ + --webroot /usr/share/nginx/html \ + --server letsencrypt \ + --accountemail support+acme@asycn.io || ACME_EXIT=$? + if [ "$ACME_EXIT" -ne 0 ] && [ "$ACME_EXIT" -ne 2 ]; then + echo "ERREUR: acme.sh a echoue (exit $ACME_EXIT)" + exit 1 + fi + docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/fullchain.cer /host/certs/$DOMAIN-cert.pem + docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/$DOMAIN.key /host/certs/$DOMAIN-key.pem + echo "Cert TLS: /host/certs/$DOMAIN-cert.pem OK (acme exit $ACME_EXIT)" + + # Etape 4 : Configuration post-deploiement (SSL, cache) + # Attend la fin de l'installation PrestaShop (ps_configuration initialisee), + # puis active SSL dans la DB (PrestaShop genere des URLs https:// grace a X-Forwarded-Proto:https de Fabio) + - name: configure + image: docker:27-cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - | + PROJECT=$(grep '^COMPOSE_PROJECT_NAME=' .env.deploy | cut -d= -f2) + DB_PASS=$(grep '^DB_PASSWORD=' .env.deploy | cut -d= -f2) + echo "Attente fin installation PrestaShop (ps_configuration)..." + MAX=60 + i=0 + until [ $i -ge $MAX ]; do + READY=$(docker exec "$PROJECT-db" mysql -uprestashop -p"$DB_PASS" -se \ + "SELECT COUNT(*) FROM prestashop.ps_configuration WHERE name='PS_SSL_ENABLED';" 2>/dev/null || echo 0) + if [ "$READY" -gt "0" ] 2>/dev/null; then + echo "Base prete, activation SSL..." + docker exec "$PROJECT-db" mysql -uprestashop -p"$DB_PASS" prestashop -e \ + "UPDATE ps_configuration SET value='1' WHERE name IN ('PS_SSL_ENABLED','PS_SSL_ENABLED_EVERYWHERE');" + docker exec "$PROJECT-app" rm -rf /var/www/html/var/cache/prod/ 2>/dev/null || true + echo "SSL active dans DB, cache efface" + break + fi + i=$((i+1)) + echo "Tentative $i/$MAX - installation en cours..." + sleep 10 + done + [ $i -ge $MAX ] && echo "AVERTISSEMENT: timeout configure SSL" || true + + # Etape 5 : Healthcheck post-deploiement + - name: healthcheck + image: alpine:3.20 + commands: + - apk add --no-cache --quiet curl + - | + SITE=$(grep '^PS_DOMAIN=' .env.deploy | cut -d= -f2) + if [ -z "$SITE" ]; then + echo "ERREUR: PS_DOMAIN non defini dans .env.deploy" + exit 1 + fi + TARGET="http://$SITE" + echo "Healthcheck sur $TARGET (max 10 minutes)..." + MAX=60 + i=0 + until [ $i -ge $MAX ]; do + CODE=$(curl -sSo /dev/null -w "%{http_code}" "$TARGET" 2>/dev/null) + echo "Tentative $((i+1))/$MAX - HTTP $CODE" + if [ "$CODE" = "200" ] || [ "$CODE" = "301" ] || [ "$CODE" = "302" ]; then + echo "PrestaShop repond correctement sur $TARGET" + exit 0 + fi + i=$((i+1)) + sleep 10 + done + echo "ERREUR: PrestaShop ne repond pas apres 10 minutes" + exit 1 + + # Notification en cas d'echec + - name: notify-failure + image: alpine:3.20 + commands: + - 'echo "ECHEC pipeline #$CI_BUILD_NUMBER sur commit $CI_COMMIT_SHA"' + - 'echo "Branche: $CI_COMMIT_BRANCH"' + when: + - status: failure diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ebe9d76 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,73 @@ +# Convention de nommage : user-project-branch (ex: syoul-prestashop-main) +# Permet plusieurs instances en parallele (prod/test/multi-user) sans collision +name: ${COMPOSE_PROJECT_NAME:-syoul-prestashop-main} + +services: + prestashop: + image: prestashop/prestashop:9.0.3-3.0-classic-8.3-apache + container_name: ${COMPOSE_PROJECT_NAME:-syoul-prestashop-main}-app + restart: always + depends_on: + db: + condition: service_healthy + environment: + DB_SERVER: db + DB_NAME: prestashop + DB_USER: prestashop + DB_PASSWD: ${DB_PASSWORD} + PS_DOMAIN: ${PS_DOMAIN} + PS_FOLDER_ADMIN: ${PS_ADMIN_FOLDER:-admin-secure} + ADMIN_MAIL: ${PRESTASHOP_ADMIN_EMAIL} + ADMIN_PASSWD: ${PRESTASHOP_ADMIN_PASSWORD} + PS_INSTALL_AUTO: "1" + PS_ERASE_DB: "0" + # SSL active : Fabio termine TLS et passe X-Forwarded-Proto:https + # PrestaShop honore ce header -> genere des URLs https:// sans boucle de redirection + PS_ENABLE_SSL: "1" + volumes: + - ps_data:/var/www/html + labels: + # Registrator lit l'IP du conteneur depuis le reseau "sonic" (-useIpFromNetwork sonic) + # et enregistre le service dans Consul avec le tag urlprefix- -> Fabio route vers ce service + # Valeurs surchargeable via env var (ex: SERVICE_80_TAGS pour changer le domaine par branche) + - SERVICE_80_NAME=${SERVICE_80_NAME:-${COMPOSE_PROJECT_NAME}-app-80} + - SERVICE_80_TAGS=${SERVICE_80_TAGS:-urlprefix-${PS_DOMAIN}/*} + # TCP check : PS_SSL_ENABLED_EVERYWHERE redirige GET / en 302 -> HTTP check failing + - SERVICE_80_CHECK_TCP=true + # sonic-acme-1 (acme-companion) emet le cert TLS et le copie dans /host/certs/ + # Fabio le detecte automatiquement par SNI pour HTTPS + - LETSENCRYPT_HOST=${PS_DOMAIN} + networks: + - prestashop-net + # Reseau "sonic" requis pour que Registrator trouve l'IP du conteneur + - sonic + + db: + image: mariadb:10.11 + container_name: ${COMPOSE_PROJECT_NAME:-syoul-prestashop-main}-db + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + MYSQL_DATABASE: prestashop + MYSQL_USER: prestashop + MYSQL_PASSWORD: ${DB_PASSWORD} + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 10 + networks: + - prestashop-net + +volumes: + ps_data: + db_data: + +networks: + prestashop-net: + driver: bridge + sonic: + # Reseau externe existant sur le serveur (partage avec Registrator/Consul/Fabio) + external: true