diff --git a/deploy_vhost.yml b/deploy_vhost.yml new file mode 100644 index 0000000..baa5309 --- /dev/null +++ b/deploy_vhost.yml @@ -0,0 +1,72 @@ +--- +- name: Récupérer l'ancienne version + ansible.builtin.shell: "dpkg-query -W -f='${Version}' {{ vhost.type }}-{{ vhost.package_name }} || echo 'N/A'" + register: old_version + changed_when: false + +- name: Installer le package + ansible.builtin.apt: + name: "{{ vhost.type }}-{{ vhost.package_name }}={{ vhost.version }}" + state: present + +- name: Récupérer la nouvelle version + ansible.builtin.shell: "dpkg-query -W -f='${Version}' {{ vhost.type }}-{{ vhost.package_name }} || echo 'N/A'" + register: new_version + changed_when: false + +- name: Notification en cas de mise à jour + block: + - name: Changer le propriétaire et permissions du projet + file: + path: /var/www/{{ vhost.package_name }} + state: directory + recurse: yes + owner: www-data + group: www-data + mode: '0750' + + - name: Créer le fichier de log + file: + path: /var/log/nginx/{{ vhost.package_name }}.log + state: touch + owner: www-data + group: www-data + mode: '0664' + + - name: Afficher l'évolution de la version + ansible.builtin.debug: + msg: "📦 Mise à jour du paquet {{ vhost.package_name }} de {{ old_version.stdout }} vers {{ new_version.stdout }}" + + - name: Envoyer notification Telegram + ansible.builtin.uri: + url: "https://api.telegram.org/bot{{ telegram_bot_token }}/sendMessage" + method: POST + body_format: json + body: + chat_id: "{{ telegram_chat_id }}" + text: | + 📦 {{ vhost.package_name }} mis à jour ! + Ancienne version : {{ old_version.stdout }} + Nouvelle version : {{ new_version.stdout }} + headers: + Content-Type: "application/json" + when: old_version.stdout != new_version.stdout + +- name: Déployer le fichier .env + ansible.builtin.template: + src: "{{ vhost.env_template | default('host.env.j2') }}" + dest: "/var/www/{{ vhost.package_name }}/.env" + owner: www-data + group: www-data + mode: "0640" + +- name: Exécuter les commandes artisan post-déploiement + ansible.builtin.shell: > + runuser -u www-data -- + /usr/bin/php{{ vhost.php_version }} artisan {{ item }} + args: + chdir: "/var/www/{{ vhost.package_name }}" + loop: "{{ vhost.artisan_post_deploy_commands | default([]) }}" + loop_control: + label: "{{ item }}" + when: vhost.artisan_post_deploy_commands is defined diff --git a/deploy_workers.yml b/deploy_workers.yml new file mode 100644 index 0000000..59e6f6b --- /dev/null +++ b/deploy_workers.yml @@ -0,0 +1,46 @@ +--- +- name: Créer le répertoire de logs + file: + path: "/var/log/laravel-worker" + state: directory + owner: www-data + group: www-data + mode: '0755' + +- name: Déployer le fichier de configuration + copy: + dest: /etc/supervisor/conf.d/{{ worker.name }}.conf + content: | + [program:{{ worker.name }}] + command=/usr/bin/php{{ worker.php_version }} /var/www/{{ worker.package_name }}/artisan queue:work --queue={{ worker.queue }}{% if worker.options is defined %} {{ worker.options }}{% endif %} + autostart=true + autorestart=true + stderr_logfile=/var/log/laravel-worker/{{ worker.name }}.err.log + stdout_logfile=/var/log/laravel-worker/{{ worker.name }}.out.log + stopasgroup=true + killasgroup=true + loop: "{{ laravel_workers }}" + loop_control: + loop_var: worker + label: "{{ worker.name }}" + +- name: Créer le fichier de log du worker + ansible.builtin.file: + path: "/var/log/laravel-worker/{{ worker.name }}.log" + state: touch + owner: www-data + group: www-data + mode: '0664' + loop: "{{ laravel_workers }}" + loop_control: + loop_var: worker + label: "{{ worker.name }}" + +- name: Recharger la configuration de supervisord + command: supervisorctl reread + +- name: Mettre à jour les processus supervisord + command: supervisorctl update + +- name: Redémarrer les workers + command: supervisorctl restart all diff --git a/flutter/android/36/Dockerfile b/flutter/android/36/Dockerfile new file mode 100644 index 0000000..0bc5b97 --- /dev/null +++ b/flutter/android/36/Dockerfile @@ -0,0 +1,52 @@ +FROM eclipse-temurin:21-jdk + +ARG ANDROID_API_VERSION=36 +ARG ANDROID_BUILD_TOOLS_VERSION=36.0.0 +ARG FLUTTER_CHANNEL=stable + +ENV ANDROID_SDK_ROOT=/opt/android-sdk \ + ANDROID_HOME=/opt/android-sdk \ + FLUTTER_HOME=/opt/flutter \ + DEBIAN_FRONTEND=noninteractive \ + HOME=/root \ + PUB_CACHE=/root/.pub-cache \ + GRADLE_USER_HOME=/root/.gradle + +ENV PATH=${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/platform-tools:${PATH} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash \ + ca-certificates \ + curl \ + git \ + libglu1-mesa \ + openssh-client \ + unzip \ + wget \ + xz-utils \ + zip \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools \ + && curl -fsSL -o /tmp/commandlinetools.zip https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip \ + && unzip -q /tmp/commandlinetools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools \ + && mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest \ + && rm /tmp/commandlinetools.zip + +RUN yes | sdkmanager --licenses >/dev/null \ + && sdkmanager \ + "platform-tools" \ + "platforms;android-${ANDROID_API_VERSION}" \ + "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ + "cmdline-tools;latest" + +RUN git clone --depth 1 --branch ${FLUTTER_CHANNEL} https://github.com/flutter/flutter.git ${FLUTTER_HOME} \ + && git config --global --add safe.directory ${FLUTTER_HOME} + +RUN flutter config --no-analytics \ + && flutter precache --android \ + && flutter doctor -v || true + +WORKDIR /workspace + +CMD ["bash"] diff --git a/flutterAndroidPipeline.groovy b/flutterAndroidPipeline.groovy new file mode 100644 index 0000000..96028cb --- /dev/null +++ b/flutterAndroidPipeline.groovy @@ -0,0 +1,121 @@ +def call(Map config = [:]) { + def packageName = config.get('name', 'flutter-app') + def apiVersion = config.get('api', '36') + def dockerImage = "jenkins/build-flutter-android:${apiVersion}" + + // URLs configurables depuis le Jenkinsfile si besoin + def debugApiBaseUrl = config.get('debugApiBaseUrl', 'http://192.168.1.37:8000/api/v1') + def releaseApiBaseUrl = config.get('releaseApiBaseUrl', 'https://laundry-dev.abpcode.fr//api/v1') + + pipeline { + agent any + + environment { + PUB_CACHE = "${WORKSPACE}/.pub-cache" + GRADLE_USER_HOME = "${WORKSPACE}/.gradle" + } + + stages { + stage('Prepare docker') { + steps { + script { + writeFile file: 'Dockerfile', text: libraryResource("dockerfiles/flutter/android/${apiVersion}/Dockerfile") + docker.build(dockerImage, "--pull=false .") + } + } + } + + stage('Checkout') { + steps { + cleanWs() + checkout scm + } + } + + stage('Flutter doctor') { + steps { + script { + docker.image(dockerImage).inside('-u root') { + sh 'git config --global --add safe.directory "$WORKSPACE" || true' + sh 'flutter --version' + sh 'flutter doctor -v' + } + } + } + } + + stage('Dependencies') { + steps { + script { + docker.image(dockerImage).inside('-u root') { + sh 'git config --global --add safe.directory "$WORKSPACE" || true' + sh 'flutter pub get' + } + } + } + } + + stage('Analyze') { + steps { + script { + docker.image(dockerImage).inside('-u root') { + sh 'git config --global --add safe.directory "$WORKSPACE" || true' + sh 'flutter analyze' + } + } + } + } + + stage('Test') { + steps { + script { + docker.image(dockerImage).inside('-u root') { + sh 'git config --global --add safe.directory "$WORKSPACE" || true' + sh 'flutter test' + } + } + } + } + + stage('Build Android') { + steps { + script { + docker.image(dockerImage).inside('-u root') { + sh 'git config --global --add safe.directory "$WORKSPACE" || true' + + if (env.TAG_NAME) { + sh """ + flutter build apk --release \ + --dart-define=API_BASE_URL=${releaseApiBaseUrl} + """ + } else { + sh """ + flutter build apk --debug \ + --dart-define=API_BASE_URL=${debugApiBaseUrl} + """ + } + } + } + } + } + + stage('Publish') { + steps { + script { + if (env.TAG_NAME) { + env.deployment = 'prod' + env.version = "${env.TAG_NAME}-${env.BUILD_NUMBER}" + sh "cp build/app/outputs/flutter-apk/app-release.apk ./${packageName}-${env.deployment}-${env.version}.apk" + } else { + env.deployment = 'debug' + env.version = "${env.BUILD_NUMBER}" + sh "cp build/app/outputs/flutter-apk/app-debug.apk ./${packageName}-${env.deployment}-${env.version}.apk" + } + + archiveArtifacts artifacts: "${packageName}-${env.deployment}-${env.version}.apk", fingerprint: true + } + } + } + } + } +} diff --git a/host-laundry.env.j2 b/host-laundry.env.j2 new file mode 100644 index 0000000..6aaef23 --- /dev/null +++ b/host-laundry.env.j2 @@ -0,0 +1,47 @@ +APP_NAME='{{ vhost.package_name }}' +APP_ENV='{{ vhost.app_env | default('production') }}' +APP_DEBUG='{{ vhost.app_debug | default('false') }}' +APP_URL='{{ vhost.app_url }}' +APP_KEY='{{ vhost.app_key }}' +APP_TIMEZONE='{{ vhost.app_timezone | default('Europe/Paris') }}' + +APP_LOCALE='fr' +APP_FALLBACK_LOCALE='fr' + +LOG_CHANNEL='stack' +LOG_STACK='single' +LOG_LEVEL='{{ vhost.app_log_lvl | default('info') }}' + +DB_CONNECTION='mysql' +DB_HOST='localhost' +DB_PORT='8001' +DB_DATABASE='{{ vhost.app_db_name }}' +DB_USERNAME='{{ vhost.app_db_user }}' +DB_PASSWORD='{{ vhost.app_db_password }}' + +SESSION_DRIVER='{{ vhost.app_session_driver | default('database') }}' +SESSION_LIFETIME='120' + +QUEUE_CONNECTION='{{ vhost.app_queue_connection | default('database') }}' +CACHE_STORE='{{ vhost.app_cache_store | default('database') }}' + +BROADCAST_CONNECTION='log' +FILESYSTEM_DISK='local' + +STRIPE_KEY='{{ vhost.app_stripe_key | default('') }}' +STRIPE_SECRET='{{ vhost.app_stripe_secret_key | default('') }}' +STRIPE_WEBHOOK_SECRET='{{ vhost.app_stripe_webhook_secret | default('') }}' + +CORS_ALLOWED_ORIGINS='{{ vhost.app_cors_allowed_origins | default(vhost.app_url) }}' + +LAVERIE_MACHINE_API_KEY='{{ vhost.app_laverie_machine_api_key | default('') }}' +LAVERIE_SIM_CYCLE_SECONDS='{{ vhost.app_laverie_sim_cycle_seconds | default('30') }}' +LAVERIE_BOOKING_FEE='{{ vhost.app_laverie_booking_fee | default('1.00') }}' +LAVERIE_BOOKING_CANCEL_GRACE_HOURS='{{ vhost.app_laverie_booking_cancel_grace_hours | default('2') }}' +LAVERIE_BOOKING_NO_SHOW_GRACE_MINUTES='{{ vhost.app_laverie_booking_no_show_grace_minutes | default('15') }}' +LAVERIE_BOOKING_PENALTY_AMOUNT='{{ vhost.app_laverie_booking_penalty_amount | default('3.00') }}' +LAVERIE_MACHINE_OFFLINE_THRESHOLD_MINUTES='{{ vhost.app_laverie_machine_offline_threshold_minutes | default('10') }}' +LAVERIE_MACHINE_OFFLINE_CHECK_ENABLED='{{ vhost.app_laverie_machine_offline_check_enabled | default('false') }}' +LAVERIE_PAYMENT_PROVIDER='{{ vhost.app_laverie_payment_provider | default('stripe') }}' +LAVERIE_TOP_UP_MIN='{{ vhost.app_laverie_top_up_min | default('5') }}' +LAVERIE_TOP_UP_MAX='{{ vhost.app_laverie_top_up_max | default('150') }}' diff --git a/vhosts.yml.txt b/vhosts.yml.txt new file mode 100644 index 0000000..5e77a98 --- /dev/null +++ b/vhosts.yml.txt @@ -0,0 +1,30 @@ + +- package_name: 'laundry-backend' + server_name: + - 'laundry-dev.abpcode.fr' + php_version: '8.3' + type: 'laravel' + version: '*' + env_template: 'host-laundry.env.j2' + app_env: 'production' + app_debug: 'false' + app_log_lvl: 'info' + app_url: 'https://laundry-dev.abpcode.fr/' + app_key: 'VFMopd0BT6Ll6tQ28gtmif4JSjnaijdU' + app_db_name: '' + app_db_user: '' + app_db_password: '' + app_queue_connection: 'database' + app_cache_store: 'database' + app_session_driver: 'database' + app_cors_allowed_origins: 'https://laundry-dev.abpcode.fr' + app_laverie_machine_api_key: 'prod-laundry-machine-api-k7x9m2n4p8q1' + app_laverie_machine_offline_check_enabled: 'false' + app_laverie_payment_provider: 'stripe' + app_stripe_key: '' + app_stripe_secret_key: '' + artisan_post_deploy_commands: + - 'migrate --force' + - 'config:cache' + - 'route:cache' + - 'view:cache' \ No newline at end of file diff --git a/vitePipeline.groovy b/vitePipeline.groovy new file mode 100644 index 0000000..f404443 --- /dev/null +++ b/vitePipeline.groovy @@ -0,0 +1,128 @@ +def call(Map config = [:]) { + def packageName = config.get('name', 'web') + def phpVersion = config.get('phpVersion', '7.4') + def dockerImage = "jenkins/build-web:${phpVersion}" + def packageType = "laravel" + + pipeline { + agent any + + stages { + stage('Prepare docker') { + steps { + script { + writeFile file: 'Dockerfile', text: libraryResource("dockerfiles/web/${phpVersion}/Dockerfile") + docker.build(dockerImage, "--pull=false .") + } + } + } + + stage('Checkout') { + steps { + cleanWs() + checkout scm + } + } + + stage('Dependencies') { + parallel { + stage('Composer') { + steps { + script { + docker.image(dockerImage).inside("-u jenkins --mount type=bind,source=${JENKINS_SSH_PATH},target=/home/jenkins/.ssh -w /app") { + dir('src') { + sh 'composer install --no-interaction --prefer-dist --no-dev -v' + } + } + } + } + } + + stage('Node') { + steps { + script { + docker.image(dockerImage).inside('-u jenkins -w /app') { + dir('src') { + sh 'npm install' + } + } + } + } + } + } + } + + stage('Build') { + steps { + script { + docker.image(dockerImage).inside('-u jenkins -w /app') { + dir('src') { + sh 'npm run prod' + } + } + } + } + } + + stage('Packaging') { + steps { + script { + if (env.TAG_NAME) { + env['packageVersion'] = sh(returnStdout: true, script: "echo ${TAG_NAME}-${BUILD_NUMBER}").trim() + } else { + env['packageVersion'] = sh(returnStdout: true, script: "echo ${BUILD_NUMBER}").trim() + } + + dir('src') { + sh """ + mkdir -p ../fpm/var/www/${packageName} + rsync -a . ../fpm/var/www/${packageName} \ + --exclude=".env" \ + --exclude=".env.testing" \ + --exclude=".env.exemple" \ + --exclude="conf.php" \ + --exclude="*test*" \ + --exclude="vendor/phpunit" \ + --exclude="phpunit.xml" \ + --exclude="package-lock.json" \ + --exclude="composer.lock" + """ + } + + dir('fpm') { + sh """ + fpm --deb-no-default-config-files -s dir -t deb -a all -m technique@abpcode.fr \ + -n ${packageType}-${packageName} \ + -v ${packageVersion} \ + --url ${GIT_URL} \ + -p ../${packageType}-${packageName}-${packageVersion}.deb ./ + """ + } + } + } + } + + stage('Publish') { + steps { + script { + archiveArtifacts artifacts: "${packageType}-${packageName}-${packageVersion}.deb", fingerprint: true + + if (env.TAG_NAME) { + def debFile = sh( + script: "ls -1 ${packageType}-${packageName}-${packageVersion}.deb", + returnStdout: true + ).trim() + + sh """ + scp -P 2224 -i /var/jenkins_home/.ssh/id_rsa -o StrictHostKeyChecking=no \ + ${debFile} jenkins@repo.abpcode.fr:/repo/pool/.tmp.deb + ssh -p 2224 -i /var/jenkins_home/.ssh/id_rsa -o StrictHostKeyChecking=no \ + jenkins@repo.abpcode.fr "mv /repo/pool/.tmp.deb /repo/pool/${debFile}" + """ + } + } + } + } + } + } +} diff --git a/web/8.3/Dockerfile b/web/8.3/Dockerfile new file mode 100644 index 0000000..a4bbf27 --- /dev/null +++ b/web/8.3/Dockerfile @@ -0,0 +1,39 @@ +# Utiliser PHP 8.3 CLI +FROM php:8.3-cli + +# Installer Git, unzip/zip et curl +RUN apt-get update && apt-get install -y \ + git \ + unzip \ + zip \ + curl \ + openssh-client \ + && rm -rf /var/lib/apt/lists/* + +# Installer Composer 2 +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# Installer Node.js +RUN curl -sL https://deb.nodesource.com/setup_22.x | bash - +RUN apt install -y nodejs + +# Créer l'utilisateur jenkins avec home et bash +RUN useradd -m -s /bin/bash jenkins + +# Créer le répertoire .ssh pour Jenkins +RUN mkdir -p /home/jenkins/.ssh \ + && chmod 700 /home/jenkins/.ssh + +# Définir l'utilisateur jenkins pour la suite +USER jenkins + +# Définir le répertoire de travail +WORKDIR /app + +# Définir GIT_SSH_COMMAND pour tous les builds +ENV GIT_SSH_COMMAND="ssh -i /home/jenkins/.ssh/id_rsa -o StrictHostKeyChecking=no" + +# Optionnel : copier un script d'entrée ou autres fichiers si nécessaire +# COPY entrypoint.sh /usr/local/bin/entrypoint.sh +# RUN chmod +x /usr/local/bin/entrypoint.sh +# ENTRYPOINT ["entrypoint.sh"]