diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 93c274a3c4308d5dbd7a77da7649c5add8ef7414..deb76f5a1413ac3fb4704cd8c31d9917575e389e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,7 @@ stages: - quality - bundle - release + - build - deploy php-dependencies: @@ -151,3 +152,16 @@ documentation: only: changes: - docs/**/* + +docker: + stage: build + trigger: + include: docker/production/.gitlab-ci.yml + strategy: depend + variables: + PARENT_PIPELINE_ID: $CI_PIPELINE_ID + only: + refs: + - develop + variables: + - $CI_PROJECT_NAMESPACE == "adaures" diff --git a/docker-compose.yml b/docker-compose.yml index 667b511edfe816135c498859102a9775936dacf8..40109536f4ed61ac30c001e686b892344c4937c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: app: build: context: . - dockerfile: Dockerfile + dockerfile: docker/development/Dockerfile container_name: app command: /bin/sh -c "crontab ./crontab && cron && service cron reload && php spark serve - 0.0.0.0" ports: diff --git a/Dockerfile b/docker/development/Dockerfile similarity index 100% rename from Dockerfile rename to docker/development/Dockerfile diff --git a/docker/production/.gitlab-ci.yml b/docker/production/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..5c9e8b22e3103375b74988bc7534a2dc1d566271 --- /dev/null +++ b/docker/production/.gitlab-ci.yml @@ -0,0 +1,20 @@ +stages: + - build + +docker-build-rolling: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + variables: + TAG: $CI_COMMIT_BRANCH + script: + - cp ${DOCKER_HUB_CONFIG} /kaniko/.docker/config.json + - /kaniko/executor --context . --dockerfile docker/production/web-server/Dockerfile --destination ${DOCKER_IMAGE_WEB_SERVER}:${TAG} + - /kaniko/executor --context . --dockerfile docker/production/app/Dockerfile --destination ${DOCKER_IMAGE_APP}:${TAG} + needs: + - pipeline: $PARENT_PIPELINE_ID + job: bundle + only: + refs: + - develop diff --git a/docker/production/app/Dockerfile b/docker/production/app/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f764a3f9a3e174cbab298a889eae2b3d2dd53276 --- /dev/null +++ b/docker/production/app/Dockerfile @@ -0,0 +1,40 @@ +FROM docker.io/alpine:3.13 AS ffmpeg-downloader + +RUN apk add --no-cache curl && \ + curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz && \ + tar -xJf ffmpeg.tar.xz && \ + mv ffmpeg-5.0.1-amd64-static ffmpeg + +FROM docker.io/php:8.0-fpm-alpine3.13 + +COPY docker/production/app/entrypoint.sh /entrypoint.sh + +COPY docker/production/app/uploads.ini /usr/local/etc/php/conf.d/uploads.ini + +RUN echo "* * * * * /usr/local/bin/php /opt/castopod/public/index.php scheduled-activities" > /crontab.txt && \ + echo "* * * * 10 /usr/local/bin/php /opt/castopod/public/index.php scheduled-video-clips" >> /crontab.txt && \ + echo "* * * * * /usr/local/bin/php /opt/castopod/public/index.php scheduled-websub-publish" >> /crontab.txt + +RUN apk add --no-cache libpng icu-libs freetype libwebp libjpeg-turbo libxpm ffmpeg && \ + apk add --no-cache --virtual .php-ext-build-dep freetype-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev libxpm-dev icu-dev && \ + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp --with-xpm && \ + docker-php-ext-install gd intl mysqli exif && \ + docker-php-ext-enable mysqli gd intl exif && \ + apk del .php-ext-build-dep + +COPY castopod /opt/castopod +COPY --from=ffmpeg-downloader /ffmpeg/ffmpeg /ffmpeg/ffprobe /ffmpeg/qt-faststart /usr/local/bin/ + +RUN chmod 544 /entrypoint.sh && \ + chmod 444 /crontab.txt && \ + /usr/bin/crontab /crontab.txt + +WORKDIR /opt/castopod + +VOLUME /opt/castopod/public/media + +EXPOSE 9000 + +ENTRYPOINT [ "sh", "-c" ] + +CMD [ "/entrypoint.sh" ] diff --git a/docker/production/app/entrypoint.sh b/docker/production/app/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..2a1ecfa8c54861f42b0f994c18ff02b80016d501 --- /dev/null +++ b/docker/production/app/entrypoint.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +if [ -z "${CP_BASEURL}" ] +then + echo "CP_BASEURL must be set" + exit 1 +fi + +if [ -z "${CP_MEDIA_BASEURL}" ] +then + echo "CP_MEDIA_BASEURL is empty, leaving empty by default" +fi + +if [ -z "${CP_ADMIN_GATEWAY}" ] +then + echo "CP_ADMIN_GATEWAY is empty, using default" + CP_ADMIN_GATEWAY="cp-admin" +fi + +if [ -z "${CP_AUTH_GATEWAY}" ] +then + echo "CP_AUTH_GATEWAY is empty, using default" + CP_AUTH_GATEWAY="cp-auth" +fi + +if [ -z "${CP_ANALYTICS_SALT}" ] +then + echo "CP_ANALYTICS_SALT is empty, this is mandatory, generate a new one with tr -dc \\!\\#-\\&\\(-\\[\\]-\\_a-\\~ </dev/urandom | head -c 64" + exit 1 +fi + +if [ -z "${CP_DATABASE_HOSTNAME}" ] +then + echo "CP_DATABASE_HOSTNAME is empty, using default" + CP_DATABASE_HOSTNAME="mariadb" +fi + +if [ -z "${CP_DATABASE_PREFIX}" ] +then + echo "CP_DATABASE_PREFIX is empty, using default" + CP_DATABASE_PREFIX="cp_" +fi + +if [ -z "${CP_DATABASE_NAME}" ] +then + if [ -z "${MYSQL_DATABASE}" ] + then + echo "When CP_DATABASE_NAME is empty, MYSQL_DATABASE must be set" + exit 1 + fi + + echo "CP_DATABASE_NAME is empty, using mysql variable" + CP_DATABASE_NAME="${MYSQL_DATABASE}" +fi + +if [ -z "${CP_DATABASE_USERNAME}" ] +then + if [ -z "${MYSQL_USER}" ] + then + echo "When CP_DATABASE_USERNAME is empty, MYSQL_USER must be set" + exit 1 + fi + + echo "CP_DATABASE_USERNAME is empty, using mysql variable" + CP_DATABASE_USERNAME="${MYSQL_USER}" +fi + +if [ -z "${CP_DATABASE_PASSWORD}" ] +then + if [ -z "${MYSQL_PASSWORD}" ] + then + echo "When CP_DATABASE_PASSWORD is empty, MYSQL_PASSWORD must be set" + exit 1 + fi + + echo "CP_DATABASE_PASSWORD is empty, using mysql variable" + CP_DATABASE_PASSWORD="${MYSQL_PASSWORD}" +fi + +if [ ! -z "${CP_REDIS_HOST}" ] +then + echo "Using redis cache handler" + CP_CACHE_HANDLER="redis" + if [ -z "${CP_REDIS_PASSWORD}" ] + then + echo "CP_REDIS_PASSWORD is empty, using default" + CP_REDIS_PASSWORD="null" + else + CP_REDIS_PASSWORD="\"${CP_REDIS_PASSWORD}\"" + fi + + if [ -z "${CP_REDIS_PORT}" ] + then + echo "CP_REDIS_PORT is empty, using default" + CP_REDIS_PORT="6379" + fi + + if [ -z "${CP_REDIS_DATABASE}" ] + then + echo "CP_REDIS_DATABASE is empty, using default" + CP_REDIS_DATABASE="0" + fi +else + echo "Using file cache handler" + CP_CACHE_HANDLER="file" +fi + +cat << EOF > /opt/castopod/.env +app.baseURL="${CP_BASEURL}" +app.mediaBaseURL="${CP_MEDIA_BASEURL}" +EOF + +if [ "${CP_DISABLE_HTTPS}" == "1" ] +then + echo "HTTPS redirection is disabled for test purpose, please enable it in production mode" + echo "app.forceGlobalSecureRequests=false" >> /opt/castopod/.env +else + echo "HTTPS redirection is enabled by default (mandatory to federate with the fediverse), use CP_DISABLE_HTTPS=1 to disable it for test purpose" +fi + +cat << EOF >> /opt/castopod/.env +admin.gateway="${CP_ADMIN_GATEWAY}" +auth.gateway="${CP_AUTH_GATEWAY}" + +analytics.salt="${CP_ANALYTICS_SALT}" + +database.default.hostname="${CP_DATABASE_HOSTNAME}" +database.default.database="${CP_DATABASE_NAME}" +database.default.username="${CP_DATABASE_USERNAME}" +database.default.password="${CP_DATABASE_PASSWORD}" +database.default.DBPrefix="${CP_DATABASE_PREFIX}" + +cache.handler="${CP_CACHE_HANDLER}" +EOF + +if [ "${CP_CACHE_HANDLER}" == "redis" ] +then + cat << EOF >> /opt/castopod/.env +cache.redis.host="${CP_REDIS_HOST}" +cache.redis.password=${CP_REDIS_PASSWORD} +cache.redis.port=${CP_REDIS_PORT} +cache.redis.database=${CP_REDIS_DATABASE} +EOF +fi + +echo "Using config:" +cat /opt/castopod/.env + +/usr/sbin/crond -f /crontab.txt -L /dev/stdout & +/usr/local/sbin/php-fpm diff --git a/docker/production/app/uploads.ini b/docker/production/app/uploads.ini new file mode 100644 index 0000000000000000000000000000000000000000..23b3c1cdf87ca6ca0ae34ba15c430029cf3116b3 --- /dev/null +++ b/docker/production/app/uploads.ini @@ -0,0 +1,5 @@ +file_uploads = On +memory_limit = 512M +upload_max_filesize = 500M +post_max_size = 512M +max_execution_time = 300 diff --git a/docker/production/web-server/Dockerfile b/docker/production/web-server/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..3b2320a246e0861b3bd7490db458096760d33b54 --- /dev/null +++ b/docker/production/web-server/Dockerfile @@ -0,0 +1,20 @@ +FROM docker.io/nginx:1.21-alpine + +VOLUME /var/www/html/media + +EXPOSE 80 + +WORKDIR /var/www/html + +COPY docker/production/web-server/entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh && \ + apk add --no-cache curl + +HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost || exit 1 + +COPY docker/production/web-server/nginx.conf /etc/nginx/nginx.conf + +COPY castopod/public /var/www/html + +CMD ["/entrypoint.sh"] diff --git a/docker/production/web-server/entrypoint.sh b/docker/production/web-server/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..4bd93726ed68df48bd072c04226afb629209a804 --- /dev/null +++ b/docker/production/web-server/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +if [ -z "${CP_HOST_BACK}" ] +then + echo "CP_HOST_BACK is empty, using default" + CP_HOST_BACK="back" +fi + +sed -i "s/CP_HOST_BACK/${CP_HOST_BACK}/" /etc/nginx/nginx.conf +nginx -g "daemon off;" diff --git a/docker/production/web-server/nginx.conf b/docker/production/web-server/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..8000c77753dfd2d7aff87bb4736258a9366a5ac9 --- /dev/null +++ b/docker/production/web-server/nginx.conf @@ -0,0 +1,76 @@ +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + + keepalive_timeout 65; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Real-IP; + + upstream php-handler { + server CP_HOST_BACK:9000; + } + + server { + listen 80; + + root /var/www/html; + + index index.php index.html index.htm; + + client_max_body_size 1G; + fastcgi_buffers 64 4K; + + gzip on; + gzip_vary on; + gzip_comp_level 4; + gzip_min_length 256; + gzip_types application/atom+xml application/javascript audio/mpeg application/rss+xml image/bmp image/png image/jpeg image/webp image/svg+xml image/x-icon video/mp4 text/css text/plain text/html; + + location ~ /.*\.(png|ico|txt|js|js\.map)$ { + try_files $uri =404; + } + + location ~ /(assets|media)/.*$ { + try_files $uri =404; + } + + location /.well-known/GDPR.yml { + try_files $uri =404; + } + + location / { + fastcgi_param SCRIPT_FILENAME /opt/castopod/public/index.php; + include fastcgi_params; + fastcgi_index index.php; + fastcgi_pass php-handler; + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_param SCRIPT_FILENAME /opt/castopod/public/$fastcgi_script_name; + include fastcgi_params; + fastcgi_index index.php; + fastcgi_pass php-handler; + } + } +}