diff --git a/.examples/docker-compose/opensocial.at/.env b/.examples/docker-compose/opensocial.at/.env new file mode 100644 index 0000000..8142ad9 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/.env @@ -0,0 +1 @@ +./friendica.conf diff --git a/.examples/docker-compose/opensocial.at/README.md b/.examples/docker-compose/opensocial.at/README.md new file mode 100644 index 0000000..3bc6778 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/README.md @@ -0,0 +1,50 @@ +# Opensocial.at setup + +This example of the current opensocial.at configuration has to be seen as a possible "production-ready" environment. +The focus of this configuration is on performance and scalability. + +## Prerequisites + +This setup needs some configuration first to be fully usable. + +1. It uses an external, dedicated database, which is not included here (you can just add a `mariadb` service directly) +2. avatar caching needs to be enabled + 1. Enable the system-config `system.avatar_cache` + 2. Set `avatar_cache_path` to `/var/www/avatar` +3. It uses a traefik docker service as overall reverse proxy for the whole docker environment + 1. Otherwise, adaptations of the two services `web` and `avatar` are necessary + +## The setup + +The setup splits Friendica in as much (micro)services as possible. + +### Split Frontend & Daemon + +This setup splits the frontend services from the background Daemon. +So it's possible to scale different aspect from the frontend without harming states of the cronjob forks of the Daemon. + +### Redis + +Redis is a highly optimized, in-memory key-value storage. + +The current setup uses redis for two use-cases: +- Redis as PHP overall session handler +- Redis for Friendica specific session-state handling + +### [app](./app) (php-fpm) + +The frontend logic of each user-request is computed by a php-fpm instance. +Because of the distributed session handling, it's possible to scale as much php-fpm app-instances as you need. + +### [web](./web) (nginx) + +This nginx instance is a reverse proxy for the frontend logic to avoid direct access to the php-fpm. +And it delivers static resources directly without passing the request to the php-fpm instance. + +### [avatar](./avatar) (nginx) + +This stateless nginx instance delivers all avatar-pictures of this instance. + +### [cron](./app) (php-fpm) + +The background daemon, which is based on the same image as the app-image. diff --git a/.examples/docker-compose/opensocial.at/app/Dockerfile b/.examples/docker-compose/opensocial.at/app/Dockerfile new file mode 100644 index 0000000..56eb0a0 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/app/Dockerfile @@ -0,0 +1,15 @@ +FROM friendica:fpm-alpine + +ENV FRIENDICA_UPGRADE=true +ENV PHP_MEMORY_LIMIT 2G + +# Use the default production configuration +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +ENV FRIENDICA_PHP_OUT="/var/www/html/php.out" + +RUN set -ex; \ + touch ${FRIENDICA_PHP_OUT:-"php.out"}; \ + chown www-data:www-data ${FRIENDICA_PHP_OUT:-"php.out"}; + +RUN sed -i 's/access.log = \/proc\/self\/fd\/2/access.log = \/proc\/self\/fd\/1/g' /usr/local/etc/php-fpm.d/docker.conf diff --git a/.examples/docker-compose/opensocial.at/avatar/Dockerfile b/.examples/docker-compose/opensocial.at/avatar/Dockerfile new file mode 100644 index 0000000..9c42d14 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/avatar/Dockerfile @@ -0,0 +1,18 @@ +FROM nginx:latest + +RUN usermod -u 82 www-data + +RUN set -ex; \ + mkdir -p /var/www/html; \ + mkdir -p /etc/nginx/snippets; + +COPY ./templates /etc/nginx/conf.d/templates +COPY nginx.conf /etc/nginx/nginx.conf + +COPY error-page.html /var/www/html/error-page.html +COPY custom-error-page.conf /etc/nginx/snippets/custom-error-page.conf + +COPY *.sh / +RUN chmod +x /*.sh + +CMD ["/cmd.sh"] diff --git a/.examples/docker-compose/opensocial.at/avatar/cmd.sh b/.examples/docker-compose/opensocial.at/avatar/cmd.sh new file mode 100644 index 0000000..dde131a --- /dev/null +++ b/.examples/docker-compose/opensocial.at/avatar/cmd.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active +nginx -qt +until ping app -c1 > /dev/null; do sleep 1; done + +exec nginx -g 'daemon off;' diff --git a/.examples/docker-compose/opensocial.at/avatar/custom-error-page.conf b/.examples/docker-compose/opensocial.at/avatar/custom-error-page.conf new file mode 100644 index 0000000..5e8232c --- /dev/null +++ b/.examples/docker-compose/opensocial.at/avatar/custom-error-page.conf @@ -0,0 +1,5 @@ +error_page 404 403 500 503 /error-page.html; +location = /error-page.html { + root /var/www/html; + internal; +} diff --git a/.examples/docker-compose/opensocial.at/avatar/error-page.html b/.examples/docker-compose/opensocial.at/avatar/error-page.html new file mode 100644 index 0000000..3b7ea51 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/avatar/error-page.html @@ -0,0 +1,94 @@ + + + + + + + +
+
+

Sorry the page can't be loaded!

+
+

Contact the site's administrator or support for assistance.

+
+
+
+ + diff --git a/.examples/docker-compose/opensocial.at/avatar/nginx.conf b/.examples/docker-compose/opensocial.at/avatar/nginx.conf new file mode 100644 index 0000000..e12c831 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/avatar/nginx.conf @@ -0,0 +1,70 @@ +## +# Friendica Nginx configuration +# by Olaf Conradi, modified by Philipp Holzer +# +worker_processes 4; + +events { + worker_connections 1024; +} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +http { + map $request_id $formatted_id { + "~*(?[0-9a-f]{8})(?[0-9a-f]{4})(?[0-9a-f]{4})(?[0-9a-f]{4})(?.*)$" "${p1}-${p2}-${p3}-${p4}-${p5}"; + } + + map $http_x_request_id $uuid { + default "${request_id}"; + ~* "${http_x_request_id}"; + } + + charset utf-8; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format logger-json escape=json '{"source": "nginx", "time": $msec, "resp_body_size": $body_bytes_sent, "host": "$http_host", "address": "$remote_addr", "request_length": $request_length, "method": "$request_method", "uri": "$request_uri", "status": $status, "user_agent": "$http_user_agent", "resp_time": $request_time, "upstream_addr": "$upstream_addr", "request_id": "$uuid"}'; + + 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 logger-json; + log_not_found off; + + # If behind reverse proxy, forwards the correct IP + 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; + set_real_ip_from fc00::/7; + real_ip_header X-Real-IP; + + server { + listen 80; + include /etc/nginx/conf.d/server_name.active; + include /etc/nginx/snippets/custom-error-page.conf; + #Uncomment the following line to include a standard configuration file + #Note that the most specific rule wins and your standard configuration + #will therefore *add* to this file, but not override it. + #include standard.conf + # allow uploads up to 20MB in size + client_max_body_size 20m; + client_body_buffer_size 128k; + + add_header X-Request-ID $uuid; + + location /avatar/ { + root /var/www/; + } + + include mime.types; + + # deny access to all dot files + location ~ /\. { + deny all; + } + } +} diff --git a/.examples/docker-compose/opensocial.at/avatar/templates/server_name.template b/.examples/docker-compose/opensocial.at/avatar/templates/server_name.template new file mode 100644 index 0000000..5b9c2bd --- /dev/null +++ b/.examples/docker-compose/opensocial.at/avatar/templates/server_name.template @@ -0,0 +1 @@ +server_name ${HOSTNAME}; diff --git a/.examples/docker-compose/opensocial.at/config/app/friendica.ini b/.examples/docker-compose/opensocial.at/config/app/friendica.ini new file mode 100644 index 0000000..c13e030 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/config/app/friendica.ini @@ -0,0 +1,6 @@ +[PHP] +memory_limit = 8G +upload_max_filesize= 10G +post_max_size = 11G +max_execution_time = 3600 +max_input_time = 3600 diff --git a/.examples/docker-compose/opensocial.at/config/app/www.overloaded.conf b/.examples/docker-compose/opensocial.at/config/app/www.overloaded.conf new file mode 100644 index 0000000..3dfad7a --- /dev/null +++ b/.examples/docker-compose/opensocial.at/config/app/www.overloaded.conf @@ -0,0 +1,11 @@ +[www] +pm = dynamic +pm.max_children=100 +pm.start_servers=10 +pm.min_spare_servers = 4 +pm.max_spare_servers = 10 +;pm.process_idle_timeout = 10s; +;pm.max_requests = 1000 + +clear_env = no +catch_workers_output = yes diff --git a/.examples/docker-compose/opensocial.at/config/secrets/mysql_database.txt b/.examples/docker-compose/opensocial.at/config/secrets/mysql_database.txt new file mode 100644 index 0000000..ce3a79c --- /dev/null +++ b/.examples/docker-compose/opensocial.at/config/secrets/mysql_database.txt @@ -0,0 +1 @@ +friendica diff --git a/.examples/docker-compose/opensocial.at/config/secrets/mysql_password.txt b/.examples/docker-compose/opensocial.at/config/secrets/mysql_password.txt new file mode 100644 index 0000000..d4e0464 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/config/secrets/mysql_password.txt @@ -0,0 +1 @@ +PLEASE_CHANGE_ME diff --git a/.examples/docker-compose/opensocial.at/config/secrets/mysql_root_password.txt b/.examples/docker-compose/opensocial.at/config/secrets/mysql_root_password.txt new file mode 100644 index 0000000..d4e0464 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/config/secrets/mysql_root_password.txt @@ -0,0 +1 @@ +PLEASE_CHANGE_ME diff --git a/.examples/docker-compose/opensocial.at/config/secrets/mysql_user.txt b/.examples/docker-compose/opensocial.at/config/secrets/mysql_user.txt new file mode 100644 index 0000000..6564da0 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/config/secrets/mysql_user.txt @@ -0,0 +1 @@ +friendica-user diff --git a/.examples/docker-compose/opensocial.at/docker-compose.yml b/.examples/docker-compose/opensocial.at/docker-compose.yml new file mode 100644 index 0000000..7e8d4b7 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/docker-compose.yml @@ -0,0 +1,148 @@ +version: '3' +services: + + redis: + image: redis + restart: always + volumes: + - friendica-redis-vol-1:/data + command: + - --save 60 1 + - --loglevel warning + + app: + build: ./app + restart: always + command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" + deploy: + replicas: 3 + resources: + limits: + cpus: '5.00' + memory: '10g' + reservations: + cpus: '1.00' + memory: '1.5g' + depends_on: + - redis + volumes: + - friendica-vol-1:/var/www/html + - friendica-avatar-1:/var/www/avatar + - ./config/app/www.overload.conf:/usr/local/etc/php-fpm.d/www.overload.conf:ro + - ./config/app/friendica.ini:/usr/local/etc/php/conf.d/friendica.ini:ro + environment: + - MYSQL_USER_FILE=/run/secrets/mysql_user + - MYSQL_PASSWORD_FILE=/run/secrets/mysql_password + - MYSQL_DATABASE_FILE=/run/secrets/mysql_database + - MYSQL_HOST=${DBHOST} + - MYSQL_PORT=${DBPORT} + - FRIENDICA_ADMIN_MAIL=${MAILNAME} + - FRIENDICA_TZ=${TZ} + - FRIENDICA_LANG=${LANGUAGE} + - FRIENDICA_UPDATE=true + - SITENAME=${SITENAME} + - SMTP=${SMTP} + - SMTP_DOMAIN=${SMTP_DOMAIN} + - SMTP_AUTH_USER=${SMTP_AUTH_USER} + - SMTP_AUTH_PASS=${SMTP_AUTH_PASS} + - SMTP_TLS=${SMTP_TLS} + - SMTP_STARTTLS=${SMTP_STARTTLS} + - REDIS_HOST=redis + - FRIENDICA_DISTRIBUTED_CACHE_DRIVER=redis + - FRIENDICA_LOGGER=syslog + - FRIENDICA_SYSLOG_FLAGS=39 + - FRIENDICA_DATA=Filesystem + - FRIENDICA_DEBUGGING=true + secrets: + - mysql_database + - mysql_user + - mysql_password + + cron: + build: ./app + restart: always + volumes: + - friendica-vol-1:/var/www/html + - friendica-avatar-1:/var/www/avatar + - ./config/app/www.overloaded.conf:/usr/local/etc/php-fpm.d/www.overloaded.conf:ro + - ./config/app/friendica.ini:/usr/local/etc/php/conf.d/friendica.ini:ro + environment: + - SITENAME=${SITENAME} + - SMTP=${SMTP} + - SMTP_DOMAIN=${SMTP_DOMAIN} + - SMTP_AUTH_USER=${SMTP_AUTH_USER} + - SMTP_AUTH_PASS=${SMTP_AUTH_PASS} + - SMTP_TLS=${SMTP_TLS} + - SMTP_STARTTLS=${SMTP_STARTTLS} + - MYSQL_HOST=${DBHOST} + - MYSQL_PORT=${DBPORT} + - MYSQL_USERNAME=${DBUSER} + - MYSQL_PASSWORD=${DBPASS} + - MYSQL_DATABASE=${DBDATA} + - FRIENDICA_ADMIN_MAIL=${MAILNAME} + - FRIENDICA_DISTRIBUTED_CACHE_DRIVER=redis + - FRIENDICA_DEBUGGING=true + - FRIENDICA_LOGLEVEL=notice + - FRIENDICA_LOGGER=syslog + - FRIENDICA_SYSLOG_FLAGS=39 + depends_on: + - app + entrypoint: /cron.sh + + avatar: + build: ./avatar + deploy: + replicas: 3 + restart: on-failure:3 + volumes: + - friendica-avatar-1:/var/www/avatar:ro + environment: + - HOSTNAME=${HOSTNAME} + networks: + - web + labels: + - "traefik.enable=true" + - "traefik.http.routers.avatar.entrypoints=websecure" + - "traefik.http.routers.domain.rule=(Host(`www.your.domain`) || Host(`your.domain`)) && PathPrefix(`/avatar`)" + - "traefik.http.routers.domain.middlewares=https-chain@file" + - "traefik.http.routers.domain.tls=true" + - "traefik.http.routers.domain.tls.certresolver=default" + + web: + build: ./web + restart: always + deploy: + replicas: 3 + volumes: + - friendica-vol-1:/var/www/html:ro + environment: + - HOSTNAME=${HOSTNAME} + depends_on: + - app + networks: + - web + - default + labels: + - "traefik.enable=true" + - "traefik.http.routers.yourdomain.entrypoints=websecure" + - "traefik.http.routers.yourdomain.rule=Host(`www.your.domain`) || Host(`your.domain`)" + - "traefik.http.routers.yourdomain.middlewares=https-chain@file" + - "traefik.http.routers.yourdomain.tls=true" + - "traefik.http.routers.yourdomain.tls.certresolver=default" + +secrets: + mysql_database: + file: ./config/secrets/mysql_database.txt + mysql_user: + file: ./config/secrets/mysql_user.txt + mysql_password: + file: ./config/secrets/mysql_password.txt + +volumes: + friendica-avatar-1: + friendica-vol-1: + friendica-redis-vol-1: + +networks: + web: + external: true diff --git a/.examples/docker-compose/opensocial.at/friendica.conf b/.examples/docker-compose/opensocial.at/friendica.conf new file mode 100644 index 0000000..427de65 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/friendica.conf @@ -0,0 +1,26 @@ +# ------------------------------ +# friendica configuration +# ------------------------------ +# example.org is _not_ a valid hostname, use a fqdn here. +HOSTNAME=example.org +# ------------------------------ +# SQL database configuration +# ------------------------------ +DBHOST=db +DBPORT=3306 + +SITENAME="My SiteName" + +# Your timezone +TZ=Europe/Berlin + +MAILNAME=admin@philipp.info + +SMTP=mail +SMTP_DOMAIN=my.domain +SMTP_AUTH_USER=smtp_user +SMTP_AUTH_PASS=smpt_pass +SMTP_TLS=true +SMTP_STARTTLS=true + +LANGUAGE=de diff --git a/.examples/docker-compose/opensocial.at/web/Dockerfile b/.examples/docker-compose/opensocial.at/web/Dockerfile new file mode 100644 index 0000000..94d93f2 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/web/Dockerfile @@ -0,0 +1,11 @@ +FROM nginx:latest + +RUN usermod -u 82 www-data + +COPY ./templates /etc/nginx/conf.d/templates +COPY nginx.conf /etc/nginx/nginx.conf + +COPY *.sh / +RUN chmod +x /*.sh + +CMD ["/cmd.sh"] diff --git a/.examples/docker-compose/opensocial.at/web/cmd.sh b/.examples/docker-compose/opensocial.at/web/cmd.sh new file mode 100644 index 0000000..dde131a --- /dev/null +++ b/.examples/docker-compose/opensocial.at/web/cmd.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active +nginx -qt +until ping app -c1 > /dev/null; do sleep 1; done + +exec nginx -g 'daemon off;' diff --git a/.examples/docker-compose/opensocial.at/web/nginx.conf b/.examples/docker-compose/opensocial.at/web/nginx.conf new file mode 100644 index 0000000..e139d24 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/web/nginx.conf @@ -0,0 +1,123 @@ +## +# Friendica Nginx configuration +# by Olaf Conradi, modified by Philipp Holzer +# +#worker_processes 4; + +events { + worker_connections 1024; +} + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +http { + map $request_id $formatted_id { + "~*(?[0-9a-f]{8})(?[0-9a-f]{4})(?[0-9a-f]{4})(?[0-9a-f]{4})(?.*)$" "${p1}-${p2}-${p3}-${p4}-${p5}"; + } + + map $http_x_request_id $uuid { + default "${request_id}"; + ~* "${http_x_request_id}"; + } + + charset utf-8; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format logger-json escape=json '{"source": "nginx", "time": $msec, "resp_body_size": $body_bytes_sent, "host": "$http_host", "address": "$remote_addr", "request_length": $request_length, "method": "$request_method", "uri": "$request_uri", "status": $status, "user_agent": "$http_user_agent", "resp_time": $request_time, "upstream_addr": "$upstream_addr", "request_id": "$uuid"}'; + + 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 logger-json; + + # If behind reverse proxy, forwards the correct IP + 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; + set_real_ip_from fc00::/7; + real_ip_header X-Real-IP; + + upstream php-handler { + server app:9000; + } + + server { + listen 80; + include /etc/nginx/conf.d/server_name.active; + + index index.php; + + root /var/www/html; + #Uncomment the following line to include a standard configuration file + #Note that the most specific rule wins and your standard configuration + #will therefore *add* to this file, but not override it. + #include standard.conf + # allow uploads up to 20MB in size + client_max_body_size 20m; + client_body_buffer_size 128k; + + proxy_set_header X-Request-ID $uuid; + add_header X-Request-ID $uuid; + + # rewrite to front controller as default rule + location / { + try_files $uri /index.php?pagename=$uri&$args; + } + + # make sure webfinger and other well known services aren't blocked + # by denying dot files and rewrite request to the front controller + location ^~ /.well-known/ { + allow all; + try_files $uri /index.php?pagename=$uri&$args; + } + + # statically serve these file types when possible + # otherwise fall back to front controller + # allow browser to cache them + # added .htm for advanced source code editor library + #location ~* \.(jpg|jpeg|gif|png|ico|css|js|htm|html|ttf|woff|svg)$ { + # expires 30d; + # try_files $uri /index.php?pagename=$uri&$args; + #} + + include mime.types; + + # block these file types + location ~* \.(tpl|md|tgz|log|out)$ { + deny all; + } + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # or a unix socket + location ~* \.php$ { + # Zero-day exploit defense. + # http://forum.nginx.org/read.php?2,88845,page=3 + # Won't work properly (404 error) if the file is not stored on this + # server, which is entirely possible with php-fpm/php-fcgi. + # Comment the 'try_files' line out if you set up php-fpm/php-fcgi on + # another machine. And then cross your fingers that you won't get hacked. + try_files $uri =404; + + # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + fastcgi_pass php-handler; + + fastcgi_read_timeout 300; + + include fastcgi_params; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTP_X_REQUEST_ID $uuid; + } + + # deny access to all dot files + location ~ /\. { + deny all; + } + } +} diff --git a/.examples/docker-compose/opensocial.at/web/templates/server_name.template b/.examples/docker-compose/opensocial.at/web/templates/server_name.template new file mode 100644 index 0000000..5b9c2bd --- /dev/null +++ b/.examples/docker-compose/opensocial.at/web/templates/server_name.template @@ -0,0 +1 @@ +server_name ${HOSTNAME};