PHP 8
37
.docker/.env.example
Normal file
@ -0,0 +1,37 @@
|
||||
# docker-compose env vars
|
||||
# @see https://docs.docker.com/compose/reference/envvars/
|
||||
COMPOSE_CONVERT_WINDOWS_PATHS=1
|
||||
|
||||
# application
|
||||
APP_GROUP_NAME=application
|
||||
|
||||
# application
|
||||
APP_SSH_PASSWORD=123456
|
||||
|
||||
# mysql
|
||||
MYSQL_DATABASE=psc
|
||||
MYSQL_PASSWORD=Wichtig1
|
||||
MYSQL_ROOT_PASSWORD=Wichtig1
|
||||
|
||||
#APP_USER_NAME=application
|
||||
#APP_USER_ID=10000
|
||||
#APP_GROUP_ID=10001
|
||||
|
||||
APP_CODE_PATH_CONTAINER=/data/www
|
||||
|
||||
APP_CODE_PATH_HOST=../../src
|
||||
APP_HOST=app.local
|
||||
NETWORKS_DRIVER=bridge
|
||||
|
||||
NGINX_HOST_HTTP_PORT=80
|
||||
NGINX_HOST_HTTPS_PORT=443
|
||||
|
||||
PHP_IDE_CONFIG=serverName=psc
|
||||
VOLUMES_DRIVER=local
|
||||
|
||||
ALPINE_VERSION=3.12
|
||||
COMPOSER_VERSION=2.2.5
|
||||
MYSQL_VERSION=10.4.8
|
||||
MONGODB_VERSION=5
|
||||
NGINX_VERSION=1.21.5-alpine
|
||||
PHP_VERSION=7.4
|
||||
47
.docker/docker-compose/docker-compose-old.yml
Normal file
@ -0,0 +1,47 @@
|
||||
version: "2"
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo
|
||||
ports:
|
||||
- "11000:27017"
|
||||
mysql:
|
||||
image: registry.gitlab.com/printshopcreator/docker/docker_mysql:latest
|
||||
ports:
|
||||
- "11001:3306"
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=Wichtig1
|
||||
- MYSQL_DATABASE=psc
|
||||
cron:
|
||||
image: registry.gitlab.com/printshopcreator/docker/docker_cron:php7
|
||||
environment:
|
||||
- SYMFONY_DECRYPTION_SECRET=ZfzbggHk012ImxwsovgF0iLkJf7pUJlMJ+uBLNTHFxbwz0iwe7STUJhAyULlDSv9unBVXfPW3DFf/VuVw6vPCQ==%
|
||||
volumes:
|
||||
- .:/data/www/old
|
||||
- .:/data/www/new
|
||||
links:
|
||||
- mysql:mysql
|
||||
- mongodb:mongodb
|
||||
php:
|
||||
image: registry.gitlab.com/printshopcreator/docker/docker_php:php7
|
||||
environment:
|
||||
- ftpUsername=papedruck
|
||||
- ftpPassword=27JSdjs62Jhs
|
||||
- ftpHost=157.90.18.62
|
||||
- ftpPort=20000
|
||||
- ftpIp=157.90.18.62
|
||||
- SYMFONY_DECRYPTION_SECRET=ZfzbggHk012ImxwsovgF0iLkJf7pUJlMJ+uBLNTHFxbwz0iwe7STUJhAyULlDSv9unBVXfPW3DFf/VuVw6vPCQ==%
|
||||
links:
|
||||
- mysql:mysql
|
||||
- mongodb:mongodb
|
||||
volumes:
|
||||
- ./psc-source-v1:/data/www/old
|
||||
- ./psc-source-v2:/data/www/new
|
||||
tp:
|
||||
image: registry.gitlab.com/printshopcreator/docker/docker_tp:v21x1x1
|
||||
web:
|
||||
image: registry.gitlab.com/printshopcreator/docker/docker_web:php7_tp
|
||||
ports:
|
||||
- "8001:80"
|
||||
volumes:
|
||||
- ./psc-source-v1:/data/www/old
|
||||
- ./psc-source-v2:/data/www/new
|
||||
19
.docker/docker-compose/docker-compose-php-base.yml
Normal file
@ -0,0 +1,19 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
php-base:
|
||||
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-base-${ENV?}:${TAG?}
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./.docker/images/php/base/Dockerfile
|
||||
args:
|
||||
- ALPINE_VERSION=${ALPINE_VERSION?}
|
||||
- APP_CODE_PATH=${APP_CODE_PATH_CONTAINER?}
|
||||
- APP_GROUP_ID=${APP_GROUP_ID?}
|
||||
- APP_GROUP_NAME=${APP_GROUP_NAME?}
|
||||
- APP_USER_ID=${APP_USER_ID?}
|
||||
- APP_USER_NAME=${APP_USER_NAME?}
|
||||
- COMPOSER_VERSION=${COMPOSER_VERSION?}
|
||||
- ENV=${ENV?}
|
||||
- TARGET_PHP_VERSION=${PHP_VERSION?}
|
||||
target: ${ENV?}
|
||||
110
.docker/docker-compose/docker-compose.local.yml
Normal file
@ -0,0 +1,110 @@
|
||||
version: '3.7'
|
||||
|
||||
networks:
|
||||
network:
|
||||
driver: ${NETWORKS_DRIVER?}
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
name: mysql-${ENV?}
|
||||
driver: ${VOLUMES_DRIVER?}
|
||||
mongodb:
|
||||
name: mongodb-${ENV?}
|
||||
driver: ${VOLUMES_DRIVER?}
|
||||
|
||||
services:
|
||||
php-fpm:
|
||||
environment:
|
||||
- PHP_IDE_CONFIG=${PHP_IDE_CONFIG?}
|
||||
# cap_add and security_opt are required to enable strace
|
||||
# @see https://stackoverflow.com/a/46676868
|
||||
cap_add:
|
||||
- "SYS_PTRACE"
|
||||
security_opt:
|
||||
- "seccomp=unconfined"
|
||||
volumes:
|
||||
- ${APP_CODE_PATH_HOST?}:${APP_CODE_PATH_CONTAINER?}
|
||||
networks:
|
||||
- network
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
php-cron:
|
||||
environment:
|
||||
- PHP_IDE_CONFIG=${PHP_IDE_CONFIG?}
|
||||
volumes:
|
||||
- ${APP_CODE_PATH_HOST?}:${APP_CODE_PATH_CONTAINER?}
|
||||
cap_add:
|
||||
- "SYS_PTRACE"
|
||||
security_opt:
|
||||
- "seccomp=unconfined"
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
|
||||
web:
|
||||
volumes:
|
||||
- ${APP_CODE_PATH_HOST?}:${APP_CODE_PATH_CONTAINER?}
|
||||
ports:
|
||||
- "${NGINX_HOST_HTTP_PORT:-80}:80"
|
||||
- "${NGINX_HOST_HTTPS_PORT:-443}:443"
|
||||
networks:
|
||||
network:
|
||||
aliases:
|
||||
- ${APP_HOST?}
|
||||
depends_on:
|
||||
- php-fpm
|
||||
tp:
|
||||
image: registry.gitlab.com/printshopcreator/docker/docker_tp:v21x1x1
|
||||
networks:
|
||||
- network
|
||||
|
||||
application:
|
||||
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/application-${ENV?}:${TAG?}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./images/php/application/Dockerfile
|
||||
args:
|
||||
- BASE_IMAGE=${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-base-${ENV?}:${TAG?}
|
||||
- APP_SSH_PASSWORD=${APP_SSH_PASSWORD?}
|
||||
- ENV=${ENV?}
|
||||
environment:
|
||||
- PHP_IDE_CONFIG=${PHP_IDE_CONFIG?}
|
||||
# cap_add and security_opt are required to enable strace
|
||||
# @see https://stackoverflow.com/a/46676868
|
||||
cap_add:
|
||||
- "SYS_PTRACE"
|
||||
security_opt:
|
||||
- "seccomp=unconfined"
|
||||
volumes:
|
||||
- ${APP_CODE_PATH_HOST?}:${APP_CODE_PATH_CONTAINER?}
|
||||
ports:
|
||||
- "${APPLICATION_SSH_HOST_PORT:-2222}:22"
|
||||
tty: true
|
||||
networks:
|
||||
- network
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
mongodb:
|
||||
image: mongo:${MONGODB_VERSION?}
|
||||
platform: linux/amd64
|
||||
volumes:
|
||||
- mongodb:/data/db
|
||||
networks:
|
||||
- network
|
||||
ports:
|
||||
- "${MONGODB_HOST_PORT:-27017}:27017"
|
||||
mysql:
|
||||
image: mariadb:${MYSQL_VERSION?}
|
||||
platform: linux/amd64
|
||||
environment:
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE:-application_db}
|
||||
- MYSQL_USER=${MYSQL_USER:-application_user}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD?}
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD?}
|
||||
- TZ=${TIMEZONE:-UTC}
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
command: mysqld --sql_mode="ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" --character-set-server=utf8 --collation-server=utf8_slovenian_ci --init-connect='SET NAMES UTF8;' --innodb-flush-log-at-trx-commit=0
|
||||
networks:
|
||||
- network
|
||||
ports:
|
||||
- "${MYSQL_HOST_PORT:-3306}:3306"
|
||||
31
.docker/docker-compose/docker-compose.yml
Normal file
@ -0,0 +1,31 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
php-fpm:
|
||||
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-fpm-${ENV?}:${TAG?}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./images/php/fpm/Dockerfile
|
||||
target: ${ENV?}
|
||||
args:
|
||||
- BASE_IMAGE=${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-base-${ENV?}:${TAG?}
|
||||
- TARGET_PHP_VERSION=${PHP_VERSION?}
|
||||
|
||||
php-cron:
|
||||
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-cron-${ENV?}:${TAG?}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./images/php/cron/Dockerfile
|
||||
target: ${ENV?}
|
||||
args:
|
||||
- BASE_IMAGE=${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/php-base-${ENV?}:${TAG?}
|
||||
|
||||
web:
|
||||
image: ${DOCKER_REGISTRY?}/${DOCKER_NAMESPACE?}/web-${ENV?}:${TAG?}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./images/nginx/Dockerfile
|
||||
target: ${ENV?}
|
||||
args:
|
||||
- NGINX_VERSION=${NGINX_VERSION?}
|
||||
- APP_CODE_PATH=${APP_CODE_PATH_CONTAINER?}
|
||||
10
.docker/images/nginx/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
ARG NGINX_VERSION
|
||||
FROM nginx:${NGINX_VERSION} as base
|
||||
|
||||
COPY --chown=nginx:nginx ./images/nginx/conf.d/default.conf /etc/nginx/conf.d
|
||||
|
||||
ARG APP_CODE_PATH
|
||||
RUN sed -i "s#__NGINX_ROOT_NEW;#$APP_CODE_PATH/new/web;#" /etc/nginx/conf.d/default.conf
|
||||
RUN sed -i "s#__NGINX_ROOT_OLD;#$APP_CODE_PATH/old/public;#" /etc/nginx/conf.d/default.conf
|
||||
|
||||
FROM base as local
|
||||
103
.docker/images/nginx/conf.d/default.conf
Normal file
@ -0,0 +1,103 @@
|
||||
server {
|
||||
index index.php index.html app.php;
|
||||
error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
|
||||
set $frontRoot __NGINX_ROOT_NEW;
|
||||
set $sfApp app.php; # Change to app.php for prod
|
||||
client_max_body_size 1000M;
|
||||
root __NGINX_ROOT_OLD;
|
||||
charset UTF-8;
|
||||
location / {
|
||||
|
||||
client_max_body_size 1024M;
|
||||
try_files $uri $uri/ @notfile;
|
||||
location ~ \.php$ {
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
#
|
||||
# Om nom nom cookies
|
||||
#
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
#
|
||||
# Custom headers and headers various browsers *should* be OK with but aren't
|
||||
#
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,apikey';
|
||||
#
|
||||
# Tell client that this pre-flight info is valid for 20 days
|
||||
#
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
|
||||
add_header Access-Control-Expose-Headers Access-Control-Allow-Origin always;
|
||||
fastcgi_temp_path /tmp/fastcgi 1 2;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass php-fpm:9000;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_param SERVER_NAME $http_host;
|
||||
include fastcgi_params;
|
||||
fastcgi_pass_header *;
|
||||
}
|
||||
}
|
||||
|
||||
location @notfile {
|
||||
rewrite ^(.*) /index.php last;
|
||||
}
|
||||
|
||||
location /apps/ { # Static files
|
||||
client_max_body_size 1024M;
|
||||
root $frontRoot;
|
||||
rewrite ^/apps/(.*)$ /$1 break;
|
||||
try_files $uri @sfFront;
|
||||
}
|
||||
|
||||
#location /w2p/ {
|
||||
# proxy_pass http://tp:8080/w2p/;
|
||||
# proxy_temp_path /tmp/proxy;
|
||||
#}
|
||||
|
||||
location @sfFront { # Symfony
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
#
|
||||
# Om nom nom cookies
|
||||
#
|
||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
#
|
||||
# Custom headers and headers various browsers *should* be OK with but aren't
|
||||
#
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,apikey';
|
||||
#
|
||||
# Tell client that this pre-flight info is valid for 20 days
|
||||
#
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Credentials true always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
|
||||
add_header Access-Control-Expose-Headers Access-Control-Allow-Origin always;
|
||||
fastcgi_temp_path /tmp/fastcgi 1 2;
|
||||
fastcgi_pass php-fpm:9000;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $frontRoot/$sfApp;
|
||||
fastcgi_param SCRIPT_NAME /apps/$sfApp;
|
||||
fastcgi_param REQUEST_URI /apps$uri?$args;
|
||||
fastcgi_param SERVER_NAME $http_host;
|
||||
fastcgi_buffers 16 16k;
|
||||
fastcgi_buffer_size 32k;
|
||||
fastcgi_param HTTPS off;
|
||||
}
|
||||
}
|
||||
35
.docker/images/nginx/nginx.conf
Normal file
@ -0,0 +1,35 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
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 off;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
client_body_temp_path /tmp 1 2;
|
||||
client_body_buffer_size 256k;
|
||||
client_body_in_file_only off;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
28
.docker/images/php/application/Dockerfile
Normal file
@ -0,0 +1,28 @@
|
||||
ARG BASE_IMAGE
|
||||
FROM ${BASE_IMAGE} as base
|
||||
|
||||
FROM base as prod
|
||||
|
||||
USER $APP_USER_NAME
|
||||
|
||||
FROM base as ci
|
||||
|
||||
USER $APP_USER_NAME
|
||||
|
||||
FROM base as local
|
||||
|
||||
#RUN apt install -y \
|
||||
# openssh-server
|
||||
#
|
||||
#ARG APP_SSH_PASSWORD
|
||||
#RUN echo "$APP_USER_NAME:$APP_SSH_PASSWORD" | chpasswd 2>&1
|
||||
#
|
||||
## Required to start sshd, otherwise the container will error out on startup with the message
|
||||
## "sshd: no hostkeys available -- exiting."
|
||||
## @see https://stackoverflow.com/a/65348102/413531
|
||||
#RUN ssh-keygen -A
|
||||
#
|
||||
## we use SSH deployment configuration in PhpStorm for local development
|
||||
#EXPOSE 22
|
||||
#
|
||||
#CMD ["/usr/sbin/sshd", "-D"]
|
||||
9
.docker/images/php/base/.bashrc
Normal file
@ -0,0 +1,9 @@
|
||||
export PS1='$(whoami):$(pwd)# '
|
||||
alias ll='ls -l'
|
||||
alias lc='ls --color=auto'
|
||||
|
||||
# see https://stackoverflow.com/questions/4188324/bash-completion-of-makefile-target
|
||||
# -h to grep to hide filenames
|
||||
# -s to grep to hide error messages
|
||||
# include Makefile and .make directory
|
||||
complete -W "\`grep -shoE '^[a-zA-Z0-9_.-]+:([^=]|$)' ?akefile .make/*.mk | sed 's/[^a-zA-Z0-9_.-]*$//' | grep -v PHONY\`" make
|
||||
227
.docker/images/php/base/Dockerfile
Normal file
@ -0,0 +1,227 @@
|
||||
ARG COMPOSER_VERSION
|
||||
ARG TARGET_PHP_VERSION
|
||||
FROM composer:${COMPOSER_VERSION} as composer
|
||||
FROM php:${TARGET_PHP_VERSION}-fpm as base
|
||||
|
||||
# make build args available as ENV variables to downstream images
|
||||
# so that we don't have to pass the same build args again
|
||||
ARG APP_USER_ID
|
||||
ARG APP_GROUP_ID
|
||||
ARG APP_USER_NAME
|
||||
ARG APP_GROUP_NAME
|
||||
ARG APP_CODE_PATH
|
||||
|
||||
ARG ENV
|
||||
ENV APP_USER_ID=${APP_USER_ID}
|
||||
ENV APP_GROUP_ID=${APP_GROUP_ID}
|
||||
ENV APP_USER_NAME=${APP_USER_NAME}
|
||||
ENV APP_GROUP_NAME=${APP_GROUP_NAME}
|
||||
ENV APP_CODE_PATH=${APP_CODE_PATH}
|
||||
ENV TARGET_PHP_VERSION=${TARGET_PHP_VERSION}
|
||||
|
||||
ENV ENV=${ENV}
|
||||
|
||||
RUN addgroup -gid $APP_GROUP_ID $APP_GROUP_NAME && \
|
||||
adduser --disabled-password --uid $APP_USER_ID --shell /bin/bash --ingroup $APP_GROUP_NAME $APP_USER_NAME && \
|
||||
mkdir -p $APP_CODE_PATH && \
|
||||
chown $APP_USER_NAME: $APP_CODE_PATH
|
||||
|
||||
# install git-secret
|
||||
# @see https://git-secret.io/installation#alpine
|
||||
ADD https://gitsecret.jfrog.io/artifactory/api/security/keypair/public/repositories/git-secret-apk /etc/apk/keys/git-secret-apk.rsa.pub
|
||||
|
||||
# FYI, we are NOT using a cache mount to store the apk cache via
|
||||
# RUN --mount=type=cache,target=/var/cache/apk ln -vs /var/cache/apk /etc/apk/cache && \
|
||||
# @see https://github.com/FernandoMiguel/BuildKit#new-dockerfile
|
||||
# @see https://wiki.alpinelinux.org/wiki/Local_APK_cache#Enabling_Local_Cache_on_HDD_installs
|
||||
# because we run --update anyways to get the latest files
|
||||
RUN apt update && \
|
||||
apt install -y \
|
||||
bash \
|
||||
git \
|
||||
git-secret \
|
||||
# required for git-secret
|
||||
gawk \
|
||||
gnupg \
|
||||
make \
|
||||
strace \
|
||||
sudo \
|
||||
vim
|
||||
|
||||
|
||||
# Install intl
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libicu-dev \
|
||||
libssl-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
libxml2-dev \
|
||||
libmagickwand-dev \
|
||||
git \
|
||||
zlib1g-dev \
|
||||
unzip \
|
||||
libzip-dev \
|
||||
mupdf-tools \
|
||||
imagemagick \
|
||||
libmcrypt-dev
|
||||
|
||||
# Install fileinfo
|
||||
RUN docker-php-ext-install -j$(nproc) fileinfo
|
||||
# Install intl
|
||||
RUN docker-php-ext-install -j$(nproc) intl
|
||||
# Install mongodb
|
||||
RUN pecl install mongodb \
|
||||
&& docker-php-ext-enable mongodb
|
||||
# Install mcrypt
|
||||
RUN pecl install mcrypt \
|
||||
&& docker-php-ext-enable mcrypt
|
||||
# Install curl
|
||||
RUN docker-php-ext-install -j$(nproc) curl
|
||||
# Install Zip
|
||||
RUN docker-php-ext-install zip
|
||||
# Install gd
|
||||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install -j$(nproc) gd
|
||||
# Install soap
|
||||
RUN docker-php-ext-install -j$(nproc) soap
|
||||
# Install imagick
|
||||
RUN pecl install imagick \
|
||||
&& docker-php-ext-enable imagick
|
||||
# Install mysql
|
||||
RUN docker-php-ext-install -j$(nproc) pdo_mysql
|
||||
# Install opcache
|
||||
RUN docker-php-ext-install -j$(nproc) opcache
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libc-client-dev libkrb5-dev libldap2-dev && \
|
||||
rm -r /var/lib/apt/lists/*
|
||||
|
||||
# Install ldap
|
||||
RUN docker-php-ext-install -j$(nproc) ldap
|
||||
|
||||
RUN docker-php-ext-configure imap --with-kerberos --with-imap-ssl && \
|
||||
docker-php-ext-install -j$(nproc) imap
|
||||
|
||||
# make bash default shell
|
||||
RUN sed -e 's;/bin/ash$;/bin/bash;g' -i /etc/passwd
|
||||
|
||||
COPY ./.docker/images/php/base/conf.d/zz-app.ini $PHP_INI_DIR/conf.d/zz-app.ini
|
||||
COPY ./.docker/images/php/base/conf.d/zz-app-${ENV}.ini $PHP_INI_DIR/conf.d/zz-ppp-${ENV}.ini
|
||||
|
||||
COPY ./.docker/images/php/base/.bashrc /home/${APP_USER_NAME}/.bashrc
|
||||
COPY ./.docker/images/php/base/.bashrc /root/.bashrc
|
||||
|
||||
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
# Fix git permission issue:
|
||||
# `git` introduced a security feature to throw an error if the parent directory
|
||||
# of the `.git` directory is owned by another user.
|
||||
# @see https://github.blog/2022-04-12-git-security-vulnerability-announced/
|
||||
# @see https://github.com/actions/checkout/issues/760
|
||||
#
|
||||
# Since we might not have full control over the owner
|
||||
# ( see e.g. https://github.com/docker/for-win/issues/12742 )
|
||||
# we will add the $APP_CODE_PATH as a "safe" directory to the global git config via
|
||||
# git config --system --add safe.directory "/path/to/git/parent/folder"
|
||||
# @see https://git-scm.com/docs/git-config/2.36.0#Documentation/git-config.txt-safedirectory
|
||||
#
|
||||
# Without this fix, git-secret will emit the error
|
||||
# git-secret: abort: not in dir with git repo. Use 'git init' or 'git clone', then in repo use 'git secret init'
|
||||
RUN git config --system --add safe.directory "$APP_CODE_PATH"
|
||||
|
||||
WORKDIR $APP_CODE_PATH
|
||||
|
||||
FROM base as codebase
|
||||
|
||||
# By only copying the composer files required to run composer install
|
||||
# the layer will be cached and only invalidated when the composer dependencies are changed
|
||||
COPY ./new/composer.json /dependencies/new/
|
||||
COPY ./new/composer.lock /dependencies/new/
|
||||
|
||||
# use a cache mount to cache the composer dependencies
|
||||
# this is essentially a cache that lives in Docker BuildKit (i.e. has nothing to do with the host system)
|
||||
RUN --mount=type=cache,target=/tmp/.composer \
|
||||
cd /dependencies/new && \
|
||||
# COMPOSER_HOME=/tmp/.composer sets the home directory of composer that
|
||||
# also controls where composer looks for the cache
|
||||
# so we don't have to download dependencies again (if they are cached)
|
||||
# @see https://stackoverflow.com/a/60518444 for the correct if-then-else syntax:
|
||||
# - end all commands with ; \
|
||||
# - except THEN and ELSE
|
||||
if [ "$ENV" == "prod" ] ; \
|
||||
then \
|
||||
# on production, we don't want test dependencies
|
||||
COMPOSER_HOME=/tmp/.composer composer install --no-scripts --no-plugins --no-progress -o --no-dev; \
|
||||
else \
|
||||
COMPOSER_HOME=/tmp/.composer composer install --no-scripts --no-plugins --no-progress -o; \
|
||||
fi
|
||||
|
||||
# copy the full codebase
|
||||
COPY . /codebase
|
||||
|
||||
# move the dependencies
|
||||
RUN mv /dependencies/vendor /codebase/new/vendor
|
||||
|
||||
# remove files we don't require in the image to keep the image size small
|
||||
RUN cd /codebase && \
|
||||
rm -rf .docker/ .build/ .infrastructure/ && \
|
||||
if [ "$ENV" == "prod" ] ; \
|
||||
then \
|
||||
# on production, we don't want tests
|
||||
rm -rf tests/; \
|
||||
fi
|
||||
|
||||
# Remove all secrets that are NOT required for the given ENV:
|
||||
# `find /codebase/.secrets -type f -print` lists all files in the .secrets directory
|
||||
# `grep -v "/\(shared\|$ENV\)/"` matches only the files that are NOT in the shared/ or $ENV/ (e.g. prod/) directories
|
||||
# `grep -v ".secret\$"` ensures that we remove all files that are NOT ending in .secret
|
||||
# FYI:
|
||||
# the "$" has to be escaped with a "\"
|
||||
# "Escaping is possible by adding a \ before the variable"
|
||||
# @see https://docs.docker.com/engine/reference/builder/#environment-replacement
|
||||
# `xargs rm -f` retrieves the remaining file and deletes them
|
||||
# FYI:
|
||||
# `xargs` is necessary to convert the stdin to args for `rm`
|
||||
# @see https://stackoverflow.com/a/20307392/413531
|
||||
# the `-f` flag is required so that `rm` doesn't fail if no files are matched
|
||||
RUN find /codebase/.secrets -type f -print | grep -v "/\(shared\|$ENV\)/" | xargs rm -f && \
|
||||
find /codebase/.secrets -type f -print | grep -v ".secret\$" | xargs rm -f && \
|
||||
# list the remaining files for debugging purposes
|
||||
find /codebase/.secrets -type f -print
|
||||
|
||||
# We need a git repository for git-secret to work (can be an empty one)
|
||||
RUN cd /codebase && \
|
||||
git init
|
||||
|
||||
FROM base as prod
|
||||
|
||||
# We will use a custom ENTRYPOINT to decrypt the secrets when the container starts.
|
||||
# This way, we can store the secrets in their encrypted form directly in the image.
|
||||
# Note: Because we defined a custom ENTRYPOINT, the default CMD of the base image
|
||||
# will be overriden. Thus, we must explicitly re-define it here via `CMD ["/bin/sh"]`.
|
||||
# This behavior is described in the docs as:
|
||||
# "If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value."
|
||||
# @see https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact
|
||||
COPY ./.docker/images/php/base/decrypt-secrets.sh /decrypt-secrets.sh
|
||||
RUN chmod +x /decrypt-secrets.sh
|
||||
CMD ["/bin/sh"]
|
||||
ENTRYPOINT ["/decrypt-secrets.sh"]
|
||||
|
||||
COPY --from=codebase --chown=$APP_USER_NAME:$APP_GROUP_NAME /codebase $APP_CODE_PATH
|
||||
|
||||
COPY --chown=$APP_USER_NAME:$APP_GROUP_NAME ./.build/build-info $APP_CODE_PATH/build-info
|
||||
|
||||
FROM base as ci
|
||||
|
||||
COPY --from=codebase --chown=$APP_USER_NAME:$APP_GROUP_NAME /codebase $APP_CODE_PATH
|
||||
|
||||
FROM base as local
|
||||
|
||||
# add app user to sudoers
|
||||
# see https://ostechnix.com/add-delete-and-grant-sudo-privileges-to-users-in-alpine-linux/ for adding sudo
|
||||
# see https://askubuntu.com/a/340669 for not requiring a sudo pw
|
||||
RUN echo "root ALL=(ALL) NOPASSWD: ALL " | tee -a "/etc/sudoers.d/users" && \
|
||||
echo "${APP_USER_NAME} ALL=(ALL) NOPASSWD: ALL " | tee -a "/etc/sudoers.d/users"
|
||||
|
||||
RUN pecl install xdebug
|
||||
6
.docker/images/php/base/conf.d/zz-app-ci.ini
Normal file
@ -0,0 +1,6 @@
|
||||
opcache.validate_timestamps = "1"
|
||||
; "disable" (recheck on every request) on dev
|
||||
opcache.revalidate_freq = "0"
|
||||
; enable assert() statements for development
|
||||
assert.exception = 1
|
||||
zend.assertions = 1
|
||||
15
.docker/images/php/base/conf.d/zz-app-local.ini
Normal file
@ -0,0 +1,15 @@
|
||||
opcache.validate_timestamps = "1"
|
||||
; "disable" (recheck on every request) on dev
|
||||
opcache.revalidate_freq = "0"
|
||||
opcache.enable = off
|
||||
; enable assert() statements for development
|
||||
assert.exception = 1
|
||||
zend.assertions = 1
|
||||
|
||||
; Note:
|
||||
display_error=1
|
||||
error_reporting=E_ALL
|
||||
zend_extension=xdebug
|
||||
xdebug.client_host=host.docker.internal
|
||||
xdebug.start_with_request=yes
|
||||
xdebug.mode=develop,debug
|
||||
6
.docker/images/php/base/conf.d/zz-app-prod.ini
Normal file
@ -0,0 +1,6 @@
|
||||
opcache.validate_timestamps = "1"
|
||||
; "disable" (recheck on every request) on dev
|
||||
opcache.revalidate_freq = "0"
|
||||
; enable assert() statements for development
|
||||
assert.exception = 1
|
||||
zend.assertions = 1
|
||||
13
.docker/images/php/base/conf.d/zz-app.ini
Normal file
@ -0,0 +1,13 @@
|
||||
; https://www.scalingphpbook.com/blog/2014/02/14/best-zend-opcache-settings.html
|
||||
opcache.enable_cli = 1
|
||||
opcache.enable = 1
|
||||
opcache.fast_shutdown = 1
|
||||
; check with find . -type f -print | grep php | wc -l
|
||||
opcache.max_accelerated_files = 25000
|
||||
opcache.validate_timestamps = 0
|
||||
opcache.memory_consumption=64
|
||||
opcache.interned_strings_buffer=12
|
||||
|
||||
memory_limit = -1
|
||||
|
||||
disable_functions =
|
||||
20
.docker/images/php/base/decrypt-secrets.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# exit immediately on error
|
||||
set -e
|
||||
|
||||
# initialize make
|
||||
make make-init ENVS="ENV=prod GPG_PASSWORD=$GPG_PASSWORD"
|
||||
|
||||
# read the secret gpg key
|
||||
make gpg-init
|
||||
|
||||
# Only decrypt files required for production
|
||||
files=$(make secret-list | grep "/\(shared\|prod\)/" | tr '\n' ' ')
|
||||
make secret-decrypt-with-password FILES="$files"
|
||||
|
||||
cp .secrets/prod/app.env .env
|
||||
|
||||
# treat this script as a "decorator" and execute any other command after running it
|
||||
# @see https://www.pascallandau.com/blog/structuring-the-docker-setup-for-php-projects/#using-entrypoint-for-pre-run-configuration
|
||||
exec "$@"
|
||||
22
.docker/images/php/cron/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
ARG BASE_IMAGE
|
||||
FROM ${BASE_IMAGE} as base
|
||||
|
||||
COPY ./images/php/cron/conf.d/psc /etc/cron.d/
|
||||
COPY ./images/php/cron/bin/ /usr/bin/
|
||||
RUN chmod +x /usr/bin/start-cron.sh
|
||||
RUN chmod +x /usr/bin/set-env.sh
|
||||
|
||||
RUN apt-get update && apt-get install -y cron rsyslog
|
||||
RUN sed -i '/imklog/s/^/#/' /etc/rsyslog.conf
|
||||
|
||||
FROM base as prod
|
||||
|
||||
USER $APP_USER_NAME
|
||||
|
||||
FROM base as ci
|
||||
|
||||
USER $APP_USER_NAME
|
||||
|
||||
FROM base as local
|
||||
|
||||
CMD ["/usr/bin/set-env.sh"]
|
||||
5
.docker/images/php/cron/bin/set-env.sh
Normal file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# start-cron.sh
|
||||
declare -p > /container.env
|
||||
|
||||
/usr/bin/start-cron.sh
|
||||
8
.docker/images/php/cron/bin/start-cron.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# start-cron.sh
|
||||
rsyslogd
|
||||
cron
|
||||
touch /var/log/cron.log
|
||||
chmod -R 0777 /data/www/new/var/cache
|
||||
chmod -R 0777 /data/www/new/var/log
|
||||
tail -F /var/log/syslog /var/log/cron.log
|
||||
5
.docker/images/php/cron/conf.d/psc
Normal file
@ -0,0 +1,5 @@
|
||||
SHELL=/bin/bash
|
||||
BASH_ENV=/container.env
|
||||
#
|
||||
* * * * * www-data cd /data/www/new/web && /usr/local/bin/php /data/www/new/bin/console --env=prod application:queue:do >> /var/log/cron.log 2>&1
|
||||
#
|
||||
21
.docker/images/php/fpm/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
ARG BASE_IMAGE
|
||||
FROM ${BASE_IMAGE} as base
|
||||
|
||||
RUN echo ${TARGET_PHP_VERSION}
|
||||
|
||||
COPY ./images/php/fpm/php-fpm.d/ /etc/php7/php-fpm.d/
|
||||
COPY ./images/php/fpm/conf.d/zz-app-fpm.ini $PHP_INI_DIR/conf.d/
|
||||
COPY ./images/php/fpm/conf.d/policy.xml /etc/ImageMagick-6/policy.xml
|
||||
|
||||
RUN sed -i "s/__APP_USER_NAME/$APP_USER_NAME/" /etc/php7/php-fpm.d/* \
|
||||
&& sed -i "s/__APP_GROUP_NAME/$APP_GROUP_NAME/" /etc/php7/php-fpm.d/*
|
||||
|
||||
USER $APP_USER_NAME
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
CMD ["php-fpm", "-F"]
|
||||
|
||||
FROM base as prod
|
||||
|
||||
FROM base as local
|
||||
89
.docker/images/php/fpm/conf.d/policy.xml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policymap [
|
||||
<!ELEMENT policymap (policy)+>
|
||||
<!ATTLIST policymap xmlns CDATA #FIXED ''>
|
||||
<!ELEMENT policy EMPTY>
|
||||
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
|
||||
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
|
||||
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
|
||||
]>
|
||||
<!--
|
||||
Configure ImageMagick policies.
|
||||
|
||||
Domains include system, delegate, coder, filter, path, or resource.
|
||||
|
||||
Rights include none, read, write, execute and all. Use | to combine them,
|
||||
for example: "read | write" to permit read from, or write to, a path.
|
||||
|
||||
Use a glob expression as a pattern.
|
||||
|
||||
Suppose we do not want users to process MPEG video images:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
|
||||
|
||||
Here we do not want users reading images from HTTP:
|
||||
|
||||
<policy domain="coder" rights="none" pattern="HTTP" />
|
||||
|
||||
The /repository file system is restricted to read only. We use a glob
|
||||
expression to match all paths that start with /repository:
|
||||
|
||||
<policy domain="path" rights="read" pattern="/repository/*" />
|
||||
|
||||
Lets prevent users from executing any image filters:
|
||||
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
|
||||
Any large image is cached to disk rather than memory:
|
||||
|
||||
<policy domain="resource" name="area" value="1GP"/>
|
||||
|
||||
Define arguments for the memory, map, area, width, height and disk resources
|
||||
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
|
||||
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
|
||||
exceeds policy maximum so memory limit is 1GB).
|
||||
|
||||
Rules are processed in order. Here we want to restrict ImageMagick to only
|
||||
read or write a small subset of proven web-safe image types:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="*" />
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
|
||||
-->
|
||||
<policymap>
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- <policy domain="system" name="memory-map" value="anonymous"/> -->
|
||||
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
|
||||
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
|
||||
<policy domain="resource" name="memory" value="256MiB"/>
|
||||
<policy domain="resource" name="map" value="512MiB"/>
|
||||
<policy domain="resource" name="width" value="16KP"/>
|
||||
<policy domain="resource" name="height" value="16KP"/>
|
||||
<!-- <policy domain="resource" name="list-length" value="128"/> -->
|
||||
<policy domain="resource" name="area" value="128MB"/>
|
||||
<policy domain="resource" name="disk" value="1GiB"/>
|
||||
<!-- <policy domain="resource" name="file" value="768"/> -->
|
||||
<!-- <policy domain="resource" name="thread" value="4"/> -->
|
||||
<!-- <policy domain="resource" name="throttle" value="0"/> -->
|
||||
<!-- <policy domain="resource" name="time" value="3600"/> -->
|
||||
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
|
||||
<policy domain="module" rights="read | write" pattern="{PS,PDF,XPS}" />
|
||||
<!-- <policy domain="delegate" rights="none" pattern="HTTPS" /> -->
|
||||
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
|
||||
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
|
||||
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
|
||||
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/> -->
|
||||
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- not needed due to the need to use explicitly by mvg: -->
|
||||
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
|
||||
<!-- use curl -->
|
||||
<policy domain="delegate" rights="none" pattern="URL" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTPS" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTP" />
|
||||
<!-- in order to avoid to get image with password text -->
|
||||
<policy domain="path" rights="none" pattern="@*"/>
|
||||
</policymap>
|
||||
11
.docker/images/php/fpm/conf.d/zz-app-fpm.ini
Normal file
@ -0,0 +1,11 @@
|
||||
; overriding defaults
|
||||
default_socket_timeout=4000
|
||||
request_terminate_timeout=4000
|
||||
max_execution_time = 4000
|
||||
max_input_vars = 10000
|
||||
memory_limit=1024M
|
||||
date.timezone=Europe/Berlin
|
||||
upload_max_filesize = 1024M
|
||||
post_max_size = 1024M
|
||||
intl.default_locale=de
|
||||
short_open_tag=On
|
||||
15
.docker/images/php/fpm/php-fpm.d/www.conf
Normal file
@ -0,0 +1,15 @@
|
||||
error_log = /proc/self/fd/2
|
||||
|
||||
[www]
|
||||
access.log = /proc/self/fd/1
|
||||
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
|
||||
catch_workers_output = yes
|
||||
|
||||
user = __APP_USER_NAME
|
||||
group = __APP_GROUP_NAME
|
||||
listen = 0.0.0.0:9000
|
||||
pm = dynamic
|
||||
pm.max_children = 5
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 1
|
||||
pm.max_spare_servers = 3
|
||||
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
!.env.example
|
||||
.env
|
||||
.idea
|
||||
src/
|
||||
22
.make/01-application-setup.mk
Normal file
@ -0,0 +1,22 @@
|
||||
##@ [Application: Setup]
|
||||
|
||||
.PHONY: setup
|
||||
setup: ## Setup the application
|
||||
"$(MAKE)" composer ARGS="--working-dir=/data/www/new install"
|
||||
"$(MAKE)" setup-db
|
||||
|
||||
.PHONY: setup-db
|
||||
setup-db: ## Setup the DB tables
|
||||
$(DOCKER_COMPOSE) cp ./.setup $(DOCKER_SERVICE_NAME_PHP_MYSQL):/setup
|
||||
$(DOCKER_COMPOSE) exec -T $(DOCKER_SERVICE_NAME_PHP_MYSQL) bash -c 'mysql --password=$$MYSQL_ROOT_PASSWORD psc < /setup/mysql/backup.sql'
|
||||
$(DOCKER_COMPOSE) cp ./.setup $(DOCKER_SERVICE_NAME_PHP_MONGODB):/setup
|
||||
$(DOCKER_COMPOSE) exec -T $(DOCKER_SERVICE_NAME_PHP_MONGODB) bash -c 'mongorestore /setup/mongodb/'
|
||||
|
||||
.PHONY: composer
|
||||
composer: ## Run composer commands. Specify the command e.g. via ARGS="install"
|
||||
$(EXECUTE_IN_APPLICATION_CONTAINER) composer $(ARGS);
|
||||
|
||||
.PHONY: phpunit
|
||||
phpunit: ## Run PHPUNIT
|
||||
$(EXECUTE_IN_APPLICATION_CONTAINER) /data/www/new/vendor/bin/phpunit;
|
||||
|
||||
31
.make/02-application-commands.mk
Normal file
@ -0,0 +1,31 @@
|
||||
##@ [Application: Commands]
|
||||
|
||||
# @see https://stackoverflow.com/a/43076457
|
||||
.PHONY: restart-php-fpm
|
||||
restart-php-fpm: ## Restart the php-fpm service
|
||||
"$(MAKE)" execute-in-container DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME_PHP_FPM) COMMAND="kill -USR2 1"
|
||||
|
||||
.PHONY: execute-in-container
|
||||
execute-in-container: ## Execute a command in a container. E.g. via "make execute-in-container DOCKER_SERVICE_NAME=php-fpm COMMAND="echo 'hello'"
|
||||
@$(if $(DOCKER_SERVICE_NAME),,$(error DOCKER_SERVICE_NAME is undefined))
|
||||
@$(if $(COMMAND),,$(error COMMAND is undefined))
|
||||
$(EXECUTE_IN_ANY_CONTAINER) $(COMMAND)
|
||||
|
||||
.PHONY: execute-in-application-container
|
||||
execute-in-application-container: ## Execute a command in a container. E.g. via "make execute-in-container DOCKER_SERVICE_NAME=php-fpm COMMAND="echo 'hello'"
|
||||
@$(if $(COMMAND),,$(error COMMAND is undefined))
|
||||
$(EXECUTE_IN_APPLICATION_CONTAINER) $(COMMAND)
|
||||
|
||||
.PHONY: copy-in-mysql
|
||||
copy-in-mysql: ## Execute a command in a container. E.g. via "make execute-in-container DOCKER_SERVICE_NAME=php-fpm COMMAND="echo 'hello'"
|
||||
@$(if $(FROM),,$(error FROM is undefined))
|
||||
@$(if $(TO),,$(error TO is undefined))
|
||||
$(COPY_IN_MYSQL_CONTAINER)
|
||||
|
||||
.PHONY: enable-xdebug
|
||||
enable-xdebug: ## Enable xdebug in the given container specified by "DOCKER_SERVICE_NAME". E.g. "make enable-xdebug DOCKER_SERVICE_NAME=php-fpm"
|
||||
"$(MAKE)" execute-in-container APP_USER_NAME="root" DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME) COMMAND="sed -i 's/.*zend_extension=xdebug/zend_extension=xdebug/' '/etc/php8/conf.d/zz-app-local.ini'"
|
||||
|
||||
.PHONY: disable-xdebug
|
||||
disable-xdebug: ## Disable xdebug in the given container specified by "DOCKER_SERVICE_NAME". E.g. "make disable-xdebug DOCKER_SERVICE_NAME=php-fpm"
|
||||
"$(MAKE)" execute-in-container APP_USER_NAME="root" DOCKER_SERVICE_NAME=$(DOCKER_SERVICE_NAME) COMMAND="sed -i 's/.*zend_extension=xdebug/;zend_extension=xdebug/' '/etc/php8/conf.d/zz-app-local.ini'"
|
||||
109
.make/03-application-qa.mk
Normal file
@ -0,0 +1,109 @@
|
||||
##@ [Application: QA]
|
||||
|
||||
# variables
|
||||
CORES?=$(shell (nproc || sysctl -n hw.ncpu) 2> /dev/null)
|
||||
|
||||
# constants
|
||||
## files
|
||||
ALL_FILES=./
|
||||
APP_FILES=src/
|
||||
TEST_FILES=tests/
|
||||
|
||||
## bash colors
|
||||
RED:=\033[0;31m
|
||||
GREEN:=\033[0;32m
|
||||
YELLOW:=\033[0;33m
|
||||
NO_COLOR:=\033[0m
|
||||
|
||||
# Tool CLI config
|
||||
PHPUNIT_CMD=php vendor/bin/phpunit
|
||||
PHPUNIT_ARGS= -c phpunit.xml
|
||||
PHPUNIT_FILES=
|
||||
PHPSTAN_CMD=php -d xdebug.mode=off -d memory_limit=-1 vendor/bin/phpstan analyse
|
||||
PHPSTAN_ARGS=--level=9
|
||||
PHPSTAN_FILES=$(APP_FILES) $(TEST_FILES)
|
||||
PHPCS_CMD=php -d xdebug.mode=off -d memory_limit=-1 vendor/bin/phpcs
|
||||
PHPCS_ARGS=--parallel=$(CORES) --standard=psr12
|
||||
PHPCS_FILES=$(APP_FILES)
|
||||
PHPCBF_CMD=php -d xdebug.mode=off -d memory_limit=-1 vendor/bin/phpcbf
|
||||
PHPCBF_ARGS=$(PHPCS_ARGS)
|
||||
PHPCBF_FILES=$(PHPCS_FILES)
|
||||
PARALLEL_LINT_CMD=php -d xdebug.mode=off -d memory_limit=-1 vendor/bin/parallel-lint
|
||||
PARALLEL_LINT_ARGS=-j 4 --exclude vendor/ --exclude .docker --exclude .git
|
||||
PARALLEL_LINT_FILES=$(ALL_FILES)
|
||||
COMPOSER_REQUIRE_CHECKER_CMD=php -d xdebug.mode=off -d memory_limit=-1 vendor/bin/composer-require-checker
|
||||
COMPOSER_REQUIRE_CHECKER_ARGS=--ignore-parse-errors
|
||||
|
||||
# call with NO_PROGRESS=true to hide tool progress (makes sense when invoking multiple tools together)
|
||||
NO_PROGRESS?=false
|
||||
ifeq ($(NO_PROGRESS),true)
|
||||
PHPSTAN_ARGS+= --no-progress
|
||||
PARALLEL_LINT_ARGS+= --no-progress
|
||||
else
|
||||
PHPCS_ARGS+= -p
|
||||
PHPCBF_ARGS+= -p
|
||||
endif
|
||||
|
||||
# Use NO_PROGRESS=false when running individual tools.
|
||||
# On NO_PROGRESS=true the corresponding tool has no output on success
|
||||
# apart from its runtime but it will still print
|
||||
# any errors that occured.
|
||||
define execute
|
||||
if [ "$(NO_PROGRESS)" = "false" ]; then \
|
||||
eval "$(EXECUTE_IN_APPLICATION_CONTAINER) bash -c 'cd new && $(1) $(2) $(3) $(4)'"; \
|
||||
else \
|
||||
START=$$(date +%s); \
|
||||
printf "%-35s" "$@"; \
|
||||
if OUTPUT=$$(eval "$(EXECUTE_IN_APPLICATION_CONTAINER) bash -c 'cd new && $(1) $(2) $(3) $(4)'" 2>&1); then \
|
||||
printf " $(GREEN)%-6s$(NO_COLOR)" "done"; \
|
||||
END=$$(date +%s); \
|
||||
RUNTIME=$$((END-START)) ;\
|
||||
printf " took $(YELLOW)$${RUNTIME}s$(NO_COLOR)\n"; \
|
||||
else \
|
||||
printf " $(RED)%-6s$(NO_COLOR)" "fail"; \
|
||||
END=$$(date +%s); \
|
||||
RUNTIME=$$((END-START)) ;\
|
||||
printf " took $(YELLOW)$${RUNTIME}s$(NO_COLOR)\n"; \
|
||||
echo "$$OUTPUT"; \
|
||||
printf "\n"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
endef
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run all tests
|
||||
@$(EXECUTE_IN_APPLICATION_CONTAINER) $(PHPUNIT_CMD) $(PHPUNIT_ARGS) $(ARGS)
|
||||
|
||||
.PHONY: phplint
|
||||
phplint: ## Run phplint on all files
|
||||
@$(call execute,$(PARALLEL_LINT_CMD),$(PARALLEL_LINT_ARGS),$(PARALLEL_LINT_FILES), $(ARGS))
|
||||
|
||||
.PHONY: phpcs
|
||||
phpcs: ## Run style check on all application files
|
||||
@$(call execute,$(PHPCS_CMD),$(PHPCS_ARGS),$(PHPCS_FILES), $(ARGS))
|
||||
|
||||
.PHONY: phpcbf
|
||||
phpcbf: ## Run style fixer on all application files
|
||||
@$(call execute,$(PHPCBF_CMD),$(PHPCBF_ARGS),$(PHPCBF_FILES), $(ARGS))
|
||||
|
||||
.PHONY: phpstan
|
||||
phpstan: ## Run static analyzer on all application and test files
|
||||
@$(call execute,$(PHPSTAN_CMD),$(PHPSTAN_ARGS),$(PHPSTAN_FILES), $(ARGS))
|
||||
|
||||
.PHONY: composer-require-checker
|
||||
composer-require-checker: ## Run dependency checker
|
||||
@$(call execute,$(COMPOSER_REQUIRE_CHECKER_CMD),$(COMPOSER_REQUIRE_CHECKER_ARGS),"", $(ARGS))
|
||||
|
||||
.PHONY: qa
|
||||
qa: ## Run code quality tools on all files
|
||||
@"$(MAKE)" -j $(CORES) -k --no-print-directory --output-sync=target qa-exec NO_PROGRESS=true
|
||||
|
||||
.PHONY: qa-exec
|
||||
qa-exec: phpstan \
|
||||
phplint \
|
||||
phpcs
|
||||
|
||||
|
||||
#composer-require-checker \
|
||||
|
||||
144
.make/04-docker.mk
Normal file
@ -0,0 +1,144 @@
|
||||
# For local builds we always want to use "latest" as tag per default
|
||||
ifeq ($(ENV),local)
|
||||
TAG:=latest
|
||||
endif
|
||||
|
||||
# Enable buildkit for docker and docker-compose by default for every environment.
|
||||
# For specific environments (e.g. MacBook with Apple Silicon M1 CPU) it should be turned off to work stable
|
||||
# - this can be done in the .make/.env file
|
||||
COMPOSE_DOCKER_CLI_BUILD?=1
|
||||
DOCKER_BUILDKIT?=1
|
||||
|
||||
export COMPOSE_DOCKER_CLI_BUILD
|
||||
export DOCKER_BUILDKIT
|
||||
|
||||
# Container names
|
||||
## must match the names used in the docker-composer.yml files
|
||||
DOCKER_SERVICE_NAME_WEB:=web
|
||||
DOCKER_SERVICE_NAME_PHP_BASE:=php-base
|
||||
DOCKER_SERVICE_NAME_PHP_FPM:=php-fpm
|
||||
DOCKER_SERVICE_NAME_PHP_CRON:=cron
|
||||
DOCKER_SERVICE_NAME_PHP_MYSQL:=mysql
|
||||
DOCKER_SERVICE_NAME_PHP_MONGODB:=mongodb
|
||||
DOCKER_SERVICE_NAME_APPLICATION:=application
|
||||
|
||||
# FYI:
|
||||
# Naming convention for images is $(DOCKER_REGISTRY)/$(DOCKER_NAMESPACE)/$(DOCKER_SERVICE_NAME)-$(ENV)
|
||||
# e.g. docker.io/dofroscra/nginx-local
|
||||
# $(DOCKER_REGISTRY)---^ ^ ^ ^ docker.io
|
||||
# $(DOCKER_NAMESPACE)-------------^ ^ ^ dofroscra
|
||||
# $(DOCKER_SERVICE_NAME)------------------^ ^ nginx
|
||||
# $(ENV)-----------------------------------------^ local
|
||||
|
||||
DOCKER_DIR:=./.docker
|
||||
DOCKER_ENV_FILE:=$(DOCKER_DIR)/.env
|
||||
DOCKER_COMPOSE_DIR:=$(DOCKER_DIR)/docker-compose
|
||||
DOCKER_COMPOSE_FILE:=$(DOCKER_COMPOSE_DIR)/docker-compose.yml
|
||||
DOCKER_COMPOSE_FILE_LOCAL:=$(DOCKER_COMPOSE_DIR)/docker-compose.local.yml
|
||||
DOCKER_COMPOSE_FILE_PHP_BASE:=$(DOCKER_COMPOSE_DIR)/docker-compose-php-base.yml
|
||||
DOCKER_COMPOSE_PROJECT_NAME:=tp_$(ENV)
|
||||
|
||||
# we need a couple of environment variables for docker-compose so we define a make-variable that we can
|
||||
# then reference later in the Makefile without having to repeat all the environment variables
|
||||
DOCKER_COMPOSE_COMMAND:=ENV=$(ENV) \
|
||||
TAG=$(TAG) \
|
||||
DOCKER_REGISTRY=$(DOCKER_REGISTRY) \
|
||||
DOCKER_NAMESPACE=$(DOCKER_NAMESPACE) \
|
||||
APP_USER_ID=$(APP_USER_ID) \
|
||||
APP_GROUP_ID=$(APP_GROUP_ID) \
|
||||
APP_USER_NAME=$(APP_USER_NAME) \
|
||||
docker compose -p $(DOCKER_COMPOSE_PROJECT_NAME) --env-file $(DOCKER_ENV_FILE)
|
||||
|
||||
DOCKER_COMPOSE:=$(DOCKER_COMPOSE_COMMAND) -f $(DOCKER_COMPOSE_FILE) -f $(DOCKER_COMPOSE_FILE_LOCAL)
|
||||
DOCKER_COMPOSE_PHP_BASE:=$(DOCKER_COMPOSE_COMMAND) -f $(DOCKER_COMPOSE_FILE_PHP_BASE)
|
||||
|
||||
EXECUTE_IN_ANY_CONTAINER?=
|
||||
EXECUTE_IN_CRON_CONTAINER?=
|
||||
EXECUTE_IN_APPLICATION_CONTAINER?=
|
||||
EXECUTE_IN_MONGODB_CONTAINER?=
|
||||
EXECUTE_IN_mYSQL_CONTAINER?=
|
||||
|
||||
COPY_IN_ANY_CONTAINER?=
|
||||
COPY_IN_CRON_CONTAINER?=
|
||||
COPY_IN_APPLICATION_CONTAINER?=
|
||||
COPY_IN_MONGODB_CONTAINER?=
|
||||
COPY_IN_mYSQL_CONTAINER?=
|
||||
|
||||
DOCKER_SERVICE_NAME?=
|
||||
FROM?=
|
||||
TO?=
|
||||
|
||||
# we can pass EXECUTE_IN_CONTAINER=true to a make invocation in order to execute the target in a docker container.
|
||||
# Caution: this only works if the command in the target is prefixed with a $(EXECUTE_IN_*_CONTAINER) variable.
|
||||
# If EXECUTE_IN_CONTAINER is NOT defined, we will check if make is ALREADY executed in a docker container.
|
||||
# We still need a way to FORCE the execution in a container, e.g. for Gitlab CI, because the Gitlab
|
||||
# Runner is executed as a docker container BUT we want to execute commands in OUR OWN docker containers!
|
||||
EXECUTE_IN_CONTAINER?=
|
||||
ifndef EXECUTE_IN_CONTAINER
|
||||
# check if 'make' is executed in a docker container, see https://stackoverflow.com/a/25518538/413531
|
||||
# `wildcard $file` checks if $file exists, see https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html
|
||||
# i.e. if the result is "empty" then $file does NOT exist => we are NOT in a container
|
||||
ifeq ("$(wildcard /.dockerenv)","")
|
||||
EXECUTE_IN_CONTAINER=true
|
||||
endif
|
||||
endif
|
||||
ifeq ($(EXECUTE_IN_CONTAINER),true)
|
||||
EXECUTE_IN_ANY_CONTAINER:=$(DOCKER_COMPOSE) exec -T --user $(APP_USER_NAME) $(DOCKER_SERVICE_NAME)
|
||||
EXECUTE_IN_APPLICATION_CONTAINER:=$(DOCKER_COMPOSE) exec -w /data/www/new -T --user $(APP_USER_NAME) $(DOCKER_SERVICE_NAME_APPLICATION)
|
||||
EXECUTE_IN_CRON_CONTAINER:=$(DOCKER_COMPOSE) exec -T --user $(APP_USER_NAME) $(DOCKER_SERVICE_NAME_PHP_CRON)
|
||||
EXECUTE_IN_MYSQL_CONTAINER:=$(DOCKER_COMPOSE) exec -T $(DOCKER_SERVICE_NAME_PHP_MYSQL)
|
||||
EXECUTE_IN_MONGODB_CONTAINER:=$(DOCKER_COMPOSE) exec -T $(DOCKER_SERVICE_NAME_PHP_MONGODB)
|
||||
|
||||
COPY_IN_ANY_CONTAINER:=$(DOCKER_COMPOSE) cp $(FROM) $(DOCKER_SERVICE_NAME):$(TO)
|
||||
COPY_IN_APPLICATION_CONTAINER:=$(DOCKER_COMPOSE) cp $(FROM) $(DOCKER_SERVICE_NAME_APPLICATION):$(TO)
|
||||
COPY_IN_CRON_CONTAINER:=$(DOCKER_COMPOSE) cp $(FROM) $(DOCKER_SERVICE_NAME_PHP_CRON):$(TO)
|
||||
COPY_IN_MYSQL_CONTAINER:=$(DOCKER_COMPOSE) cp $(FROM) $(DOCKER_SERVICE_NAME_PHP_MYSQL):$(TO)
|
||||
COPY_IN_MONGODB_CONTAINER:=$(DOCKER_COMPOSE) cp $(FROM) $(DOCKER_SERVICE_NAME_PHP_MONGODB):$(TO)
|
||||
endif
|
||||
|
||||
|
||||
##@ [Docker]
|
||||
|
||||
.PHONY: docker-clean
|
||||
docker-clean: ## Remove the .env file for docker
|
||||
@rm -f $(DOCKER_ENV_FILE)
|
||||
|
||||
.PHONY: validate-docker-variables
|
||||
validate-docker-variables: .docker/.env
|
||||
@$(if $(TAG),,$(error TAG is undefined))
|
||||
@$(if $(ENV),,$(error ENV is undefined))
|
||||
@$(if $(DOCKER_REGISTRY),,$(error DOCKER_REGISTRY is undefined - Did you run make-init?))
|
||||
@$(if $(DOCKER_NAMESPACE),,$(error DOCKER_NAMESPACE is undefined - Did you run make-init?))
|
||||
@$(if $(APP_USER_ID),,$(error APP_USER_ID is undefined - Did you run make-init?))
|
||||
@$(if $(APP_GROUP_ID),,$(error APP_GROUP_ID is undefined - Did you run make-init?))
|
||||
@$(if $(APP_USER_NAME),,$(error APP_USER_NAME is undefined - Did you run make-init?))
|
||||
|
||||
.docker/.env:
|
||||
@cp $(DOCKER_ENV_FILE).example $(DOCKER_ENV_FILE)
|
||||
|
||||
.PHONY:docker-build-image
|
||||
docker-build-image: validate-docker-variables ## Build all docker images OR a specific image by providing the service name via: make docker-build DOCKER_SERVICE_NAME=<service>
|
||||
$(DOCKER_COMPOSE) build $(DOCKER_SERVICE_NAME)
|
||||
|
||||
.PHONY: docker-build-php
|
||||
docker-build-php: validate-docker-variables ## Build the php base image
|
||||
$(DOCKER_COMPOSE_PHP_BASE) build $(DOCKER_SERVICE_NAME_PHP_BASE)
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build: docker-build-php docker-build-image ## Build the php image and then all other docker images
|
||||
|
||||
.PHONY: docker-up
|
||||
docker-up: validate-docker-variables ## Create and start all docker containers. To create/start only a specific container, use DOCKER_SERVICE_NAME=<service>
|
||||
$(DOCKER_COMPOSE) up -d $(DOCKER_SERVICE_NAME)
|
||||
|
||||
.PHONY: docker-down
|
||||
docker-down: validate-docker-variables ## Stop and remove all docker containers.
|
||||
@$(DOCKER_COMPOSE) down
|
||||
|
||||
.PHONY: docker-config
|
||||
docker-config: validate-docker-variables ## List the configuration
|
||||
@$(DOCKER_COMPOSE) config
|
||||
|
||||
.PHONY: docker-prune
|
||||
docker-prune: ## Remove ALL unused docker resources, including volumes
|
||||
@docker system prune -a -f --volumes
|
||||
5
.make/05-symfony.mk
Normal file
@ -0,0 +1,5 @@
|
||||
##@ [Application: Symfony]
|
||||
|
||||
.PHONY: console
|
||||
console: ## Run composer commands. Specify the command e.g. via ARGS="install"
|
||||
$(EXECUTE_IN_APPLICATION_CONTAINER) php "/data/www/new/bin/console" $(ARGS);
|
||||
26
.make/variables.env
Normal file
@ -0,0 +1,26 @@
|
||||
DOCKER_REGISTRY=gitlab.com/boonkerz
|
||||
DOCKER_NAMESPACE=boonkerz
|
||||
|
||||
APP_USER_NAME=application
|
||||
APP_USER_ID=10000
|
||||
APP_GROUP_ID=10001
|
||||
APP_CODE_PATH_CONTAINER=/data/www
|
||||
|
||||
|
||||
DOCKER_SERVICE_NAME_WEB:=web
|
||||
DOCKER_SERVICE_NAME_PHP_BASE:=php-base
|
||||
DOCKER_SERVICE_NAME_PHP_FPM:=php-fpm
|
||||
DOCKER_SERVICE_NAME_PHP_CRON:=php-cron
|
||||
DOCKER_SERVICE_NAME_APPLICATION:=application
|
||||
DOCKER_SERVICE_NAME_MYSQL:=mysql
|
||||
DOCKER_SERVICE_NAME_MONGODB:=mongodb
|
||||
|
||||
# VM / instance names
|
||||
VM_NAME_APPLICATION=$(DOCKER_SERVICE_NAME_APPLICATION)-vm
|
||||
VM_NAME_PHP_FPM=$(DOCKER_SERVICE_NAME_PHP_FPM)-vm
|
||||
VM_NAME_PHP_CRON=$(DOCKER_SERVICE_NAME_PHP_CRON)-vm
|
||||
VM_NAME_WEB=$(DOCKER_SERVICE_NAME_WEB)-vm
|
||||
VM_NAME_MYSQL=$(DOCKER_SERVICE_NAME_MYSQL)-vm
|
||||
VM_NAME_MONGODB=$(DOCKER_SERVICE_NAME_MONGODB)-vm
|
||||
# Helpers
|
||||
ALL_VM_SERVICE_NAMES=$(VM_NAME_APPLICATION):$(DOCKER_SERVICE_NAME_APPLICATION) $(VM_NAME_PHP_FPM):$(DOCKER_SERVICE_NAME_PHP_FPM) $(VM_NAME_PHP_CRON):$(DOCKER_SERVICE_NAME_PHP_CRON) $(VM_NAME_WEB):$(DOCKER_SERVICE_NAME_WEB)
|
||||
BIN
.setup/media/612a096de36f4d50f82c7295/abriss-a4.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
.setup/media/612a0a32e36f4d50f82c7296/abriss-a4.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
.setup/media/612a0a3abc4ef8348d3f0ae7/abriss-a4-klebend.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
.setup/media/612a0a40e36f4d50f82c7297/abriss-a4-klebend.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
.setup/media/612a0a47bc4ef8348d3f0ae8/aufkleber.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
.setup/media/612a0a4ae36f4d50f82c7298/aufkleber.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
.setup/media/612a0a59bc4ef8348d3f0ae9/beachflag.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
.setup/media/612a0a61e36f4d50f82c7299/beachflag.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
.setup/media/612a0a64bc4ef8348d3f0aea/bericht-a4.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
.setup/media/612a0a6fe36f4d50f82c729a/bericht-a4.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
.setup/media/612a0a75bc4ef8348d3f0aeb/bericht-a5.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
.setup/media/612a0a84dce6920e443e6224/bierdeckel.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
.setup/media/612a0a8fbc4ef8348d3f0aec/briefpapier.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
.setup/media/612a0a99bc4ef8348d3f0aed/broschuere.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
.setup/media/612a0a9edce6920e443e6225/bericht-a5.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
.setup/media/612a0aa4bc4ef8348d3f0aee/broschuere-a4.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
.setup/media/612a0aabdce6920e443e6226/bierdeckel.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
.setup/media/612a0aaebc4ef8348d3f0aef/broschuere-a5.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
.setup/media/612a0ab8e36f4d50f82c729b/buch-a4.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
.setup/media/612a0ac0dce6920e443e6227/briefpapier.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
.setup/media/612a0ac2e36f4d50f82c729c/buch-a5.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
.setup/media/612a0acadce6920e443e6228/buch-b5.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
.setup/media/612a0acde36f4d50f82c729d/broschuere.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
.setup/media/612a0ad4dce6920e443e6229/buch-softcover.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
.setup/media/612a0adebc4ef8348d3f0af0/cd.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
.setup/media/612a0ae6bc4ef8348d3f0af1/broschuere-a4.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
.setup/media/612a0ae8dce6920e443e622a/cd-booklet.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
.setup/media/612a0af1dce6920e443e622b/cd-produktionen.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
.setup/media/612a0afbdce6920e443e622c/checkkarten.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
.setup/media/612a0afddce6920e443e622d/broschuere-a5.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
.setup/media/612a0b04bc4ef8348d3f0af2/copypapier.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
.setup/media/612a0b0ebc4ef8348d3f0af3/din-plots.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
.setup/media/612a0b18bc4ef8348d3f0af4/dinlang-flyer-gefalzt.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
.setup/media/612a0b21bc4ef8348d3f0af5/dvd.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
.setup/media/612a0b26bc4ef8348d3f0af6/buch-a4.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
.setup/media/612a0b2ce36f4d50f82c729e/dvd-booklet.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
.setup/media/612a0b32e36f4d50f82c729f/buch-a5.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
.setup/media/612a0b37dce6920e443e622e/dvd-produktionen.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
.setup/media/612a0b40dce6920e443e622f/eintrittskarte.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
.setup/media/612a0b44dce6920e443e6230/buch-softcover.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
.setup/media/612a0b49e36f4d50f82c72a0/examenstitel.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
.setup/media/612a0b53e36f4d50f82c72a1/cd.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
.setup/media/612a0b57bc4ef8348d3f0af7/faltflyer-dl-hoch-8s.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.setup/media/612a0b60bc4ef8348d3f0af8/cd-booklet.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
.setup/media/612a0b67bc4ef8348d3f0af9/faltflyer-dl-quer-8s.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
.setup/media/612a0b6dbc4ef8348d3f0afa/cd-produktionen.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
.setup/media/612a0b70e36f4d50f82c72a2/flagge.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.setup/media/612a0b79dce6920e443e6231/flyer.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
.setup/media/612a0b7bdce6920e443e6232/checkkarten.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
.setup/media/612a0b84dce6920e443e6233/fotobuch-eckig.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
.setup/media/612a0b89dce6920e443e6234/copypapier.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 61 KiB |
BIN
.setup/media/612a0b97dce6920e443e6236/din-plots.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
.setup/media/612a0b9cdce6920e443e6237/fotobuch-leder-eckig.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
.setup/media/612a0ba5dce6920e443e6238/dinlang-flyer-gefalzt.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
.setup/media/612a0ba7dce6920e443e6239/fotobuch-softcover-a4.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 54 KiB |
BIN
.setup/media/612a0bbbbc4ef8348d3f0afc/dvd.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
.setup/media/612a0bc3bc4ef8348d3f0afd/frei-plots.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
.setup/media/612a0bc8bc4ef8348d3f0afe/dvd-booklet.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
.setup/media/612a0bcebc4ef8348d3f0aff/freieform-aufkleber.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
.setup/media/612a0bd5bc4ef8348d3f0b00/dvd-produktionen.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
.setup/media/612a0bd9bc4ef8348d3f0b01/freiform-wallmount.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
.setup/media/612a0be0bc4ef8348d3f0b02/eintrittskarte.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 18 KiB |
BIN
.setup/media/612a0beebc4ef8348d3f0b04/examenstitel.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 23 KiB |
BIN
.setup/media/612a0bfabc4ef8348d3f0b06/faltflyer-dl-hoch-8s.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.setup/media/612a0bfebc4ef8348d3f0b07/grusskarten.png
Normal file
|
After Width: | Height: | Size: 68 KiB |