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..6a5dc46 --- /dev/null +++ b/.examples/docker-compose/opensocial.at/README.md @@ -0,0 +1,49 @@ +# Opensocial.at setup + +This configuration running at https://opensocial.at is an example of "production-ready" environment. +It focuses on performance and scalability. + +## Prerequisites + +This setup needs some configuration first to be usable as-is. + +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 many services as possible. + +### Split Frontend & Daemon + +This setup splits the frontend services from the background daemon so that it's possible to scale the different aspects of the frontend without harming the state 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 features: +- PHP native session handling. +- Friendica-specific session handling. + +### [app](./app) (php-fpm) + +Each incoming HTTP request is processed by a php-fpm instance. +Thanks to the distributed session handling, it's possible to spawn as many `app` instances as you need. + +### [web](./web) (nginx) + +This nginx instance is a reverse proxy for incoming HTTP requests. +It serves static resources directly and passes the script requests to the php-fpm instance. + +### [avatar](./avatar) (nginx) + +This stateless nginx instance serves all avatar pictures of this Friendica node. + +### [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};