diff --git a/.dockerignore b/.dockerignore index 6c20eadd1..60dc31a9b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,21 +11,24 @@ **/.gitattributes **/.gitignore **/.gitmodules -**/compose.*.yaml -**/compose.*.yml -**/compose.yaml -**/compose.yml **/Dockerfile **/Thumbs.db +compose.*.yaml +compose.*.yml +compose.yaml +compose.yml .github/ -docker/compose.override.yml +node_modules/ docker/storage/ docs/ +public/build/ public/bundles/ +public/media/ tests/ tools/ var/ vendor/ +.idea/ .editorconfig .env.*.local .env.local diff --git a/.env.dev_docker b/.env.dev_docker new file mode 100644 index 000000000..5799ffe58 --- /dev/null +++ b/.env.dev_docker @@ -0,0 +1,190 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +# Mbin variables +SERVER_NAME=:80 +KBIN_DOMAIN=127.0.0.1:8008 +KBIN_TITLE=Mbin +KBIN_DEFAULT_LANG=en +KBIN_FEDERATION_ENABLED=true +KBIN_CONTACT_EMAIL=contact@mbin.domain.tdl +KBIN_SENDER_EMAIL=noreply@mbin.domain.tdl +KBIN_JS_ENABLED=true +KBIN_REGISTRATIONS_ENABLED=true +KBIN_API_ITEMS_PER_PAGE=25 +KBIN_STORAGE_URL=http://127.0.0.1:8000/media +KBIN_META_TITLE="Mbin" +KBIN_META_DESCRIPTION="content aggregator, content voting, discussion and micro-blogging platform on the fediverse" +KBIN_META_KEYWORDS="mbin, content aggregator, open source, fediverse" +KBIN_HEADER_LOGO=false +KBIN_FEDERATION_PAGE_ENABLED=true +MBIN_DEFAULT_THEME=default +MBIN_HOME=/var/www/mbin +MBIN_USER=mbin +MBIN_GROUP=www-data + +# If you are running Mbin behind a reverse proxy, uncomment the line below and adjust the proxy address/range below +# to your server's IP address if it does not already fall within the private IP spaces specified. +TRUSTED_PROXIES=::1,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 +#TRUSTED_PROXIES= + +# Max image filesize (in bytes) +# This should be set to <= `upload_max_filesize` and `post_max_size` in the server's php.ini file +MAX_IMAGE_BYTES=6000000 + +# Captcha (also enable in admin panel/settings) +KBIN_CAPTCHA_ENABLED=false + +###> meteo-concept/hcaptcha-bundle ### +HCAPTCHA_SITE_KEY= +HCAPTCHA_SECRET= +###< meteo-concept/hcaptcha-bundle ### + +# Redis +REDIS_PASSWORD=!ChangeThisRedisPass! +REDIS_DNS=redis://${REDIS_PASSWORD}@redis:6379 + +# S3 storage (optional) +S3_KEY= +S3_SECRET= +S3_BUCKET= +S3_REGION= +S3_ENDPOINT= +S3_VERSION= + +# Only let admins generate oauth clients +KBIN_ADMIN_ONLY_OAUTH_CLIENTS=false + +# oAuth (optional) +OAUTH_AZURE_ID= +OAUTH_AZURE_SECRET= +# If you want people from an enterprise to connect your instance, set the tenant id here. +# If you want people from anywhere to connect with either their personnal or professionnal microsoft account, use "common" +OAUTH_AZURE_TENANT= +OAUTH_FACEBOOK_ID= +OAUTH_FACEBOOK_SECRET= +OAUTH_GOOGLE_ID= +OAUTH_GOOGLE_SECRET= +OAUTH_DISCORD_ID= +OAUTH_DISCORD_SECRET= +OAUTH_GITHUB_ID= +OAUTH_GITHUB_SECRET= +OAUTH_KEYCLOAK_ID= +OAUTH_KEYCLOAK_SECRET= +OAUTH_KEYCLOAK_URI= +OAUTH_KEYCLOAK_REALM= +OAUTH_KEYCLOAK_VERSION= +OAUTH_SIMPLELOGIN_ID= +OAUTH_SIMPLELOGIN_SECRET= +OAUTH_ZITADEL_ID= +OAUTH_ZITADEL_SECRET= +OAUTH_ZITADEL_BASE_URL= +OAUTH_AUTHENTIK_ID= +OAUTH_AUTHENTIK_SECRET= +OAUTH_AUTHENTIK_BASE_URL= +OAUTH_PRIVACYPORTAL_ID= +OAUTH_PRIVACYPORTAL_SECRET= + +# If true, sign ins and sign ups will only be possible through the OAuth providers configured above +SSO_ONLY_MODE= + +# image exif cleaning options +# available value: none, sanitize, scrub +# can be set differently for user uploaded and external media +EXIF_CLEAN_MODE_UPLOADED=sanitize +EXIF_CLEAN_MODE_EXTERNAL=none +# path to exiftool binary, leave blank for auto PATH search +EXIF_EXIFTOOL_PATH= +# max execution time for exiftool in seconds, defaults to 10 seconds +EXIF_EXIFTOOL_TIMEOUT=10 + +###> caddy ### +PHP_FASTCGI_HOST=php:9000 +###< caddy ### + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=427f5e2940e5b2472c1b44b2d06e0525 +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +POSTGRES_HOST=db:5432 +POSTGRES_DB=mbin +POSTGRES_USER=mbin +POSTGRES_PASSWORD=!ChangeThisPostgresPass! +# IMPORTANT: You MUST configure your PostgreSQL server version! +POSTGRES_VERSION=16 +DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8" +###< doctrine/doctrine-bundle ### + +###> rabbitmq ### +RABBITMQ_DEFAULT_USER=mbin +RABBITMQ_DEFAULT_PASS=!ChangeThisRabbitPass! +###< rabbitmq ### + +###> symfony/messenger ### +# Choose one of the transports below +MESSENGER_TRANSPORT_DSN=amqp://mbin:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/%2f/messages +#MESSENGER_TRANSPORT_DSN=doctrine://default +#MESSENGER_TRANSPORT_DSN=redis://${REDIS_PASSWORD}@${REDIS_HOST}/messages +###< symfony/messenger ### + +###> symfony/mailer ### +# See https://symfony.com/doc/current/mailer.html#using-built-in-transports +# MAILER_DSN=sendmail://default # Use sendmail when you are using Postfix +MAILER_DSN=smtp://mailserver # Use a SMTP Docker service called 'mailserver' (see compose.yml) +# Explicitly url encode any character in username and password +# %40 = @ +# Gmail: +# MAILER_DSN=gmail+smtp://user%40domain.com:pass@default +# Our own SMTP server: +# MAILER_DSN=smtp://user%40domain.com:pass@smtp.example.com:port +###< symfony/mailer ### + +###> symfony/mailgun-mailer ### +# MAILER_DSN=mailgun://KEY:DOMAIN@default?region=us +# MAILER_DSN=mailgun+smtp://postmaster@sandboxxx.mailgun.org:key@default?region=us +###< symfony/mailgun-mailer ### + +###> symfony/mercure-bundle ### +# See https://symfony.com/doc/current/mercure.html#configuration +# The URL of the Mercure hub, used by the app to publish updates (can be a local URL) +# Assuming you are running Mercure Caddy on port 3000 +MERCURE_URL=http://www:80/.well-known/mercure +# The public URL of the Mercure hub, used by the browser to connect +MERCURE_PUBLIC_URL=https://${KBIN_DOMAIN}/.well-known/mercure +# The secret used to sign the JWTs +MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!" +MERCURE_PUBLISHER_JWT_KEY=${MERCURE_JWT_SECRET} +MERCURE_SUBSCRIBER_JWT_KEY=${MERCURE_JWT_SECRET} +###< symfony/mercure-bundle ### + +###> nelmio/cors-bundle ### +CORS_ALLOW_ORIGIN="^https?://(${KBIN_DOMAIN}|127\.0\.0\.1)(:[0-9]+)?$" +###< nelmio/cors-bundle ### + +###> symfony/lock ### +# Choose one of the stores below +# postgresql+advisory://db_user:db_password@localhost/db_name +LOCK_DSN=flock +###< symfony/lock ### + +###> league/oauth2-server-bundle ### +OAUTH_PRIVATE_KEY= +OAUTH_PUBLIC_KEY= +OAUTH_PASSPHRASE= +OAUTH_ENCRYPTION_KEY= +###< league/oauth2-server-bundle ### diff --git a/.env.example_docker b/.env.example_docker index 370f106ca..54a374bd3 100644 --- a/.env.example_docker +++ b/.env.example_docker @@ -31,6 +31,10 @@ KBIN_META_KEYWORDS="mbin, content aggregator, open source, fediverse" KBIN_HEADER_LOGO=false KBIN_FEDERATION_PAGE_ENABLED=true MBIN_DEFAULT_THEME=default +MBIN_HOME=/var/www/mbin +MBIN_SRC=/usr/src/mbin +MBIN_USER=mbin +MBIN_GROUP=www-data # If you are running Mbin behind a reverse proxy, uncomment the line below and adjust the proxy address/range below # to your server's IP address if it does not already fall within the private IP spaces specified. @@ -113,6 +117,10 @@ EXIF_EXIFTOOL_PATH= # max execution time for exiftool in seconds, defaults to 10 seconds EXIF_EXIFTOOL_TIMEOUT=10 +###> caddy ### +PHP_FASTCGI_HOST=php:9000 +###< caddy ### + ###> symfony/framework-bundle ### APP_ENV=prod APP_SECRET=!CHANGE_SECRET! @@ -121,18 +129,22 @@ APP_SECRET=!CHANGE_SECRET! ###> doctrine/doctrine-bundle ### # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url POSTGRES_HOST=db:5432 -POSTGRES_DB=kbin -POSTGRES_USER=kbin +POSTGRES_DB=mbin +POSTGRES_USER=mbin POSTGRES_PASSWORD=!ChangeThisPostgresPass! # IMPORTANT: You MUST configure your PostgreSQL server version! -POSTGRES_VERSION=13 +POSTGRES_VERSION=16 DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}?serverVersion=${POSTGRES_VERSION}&charset=utf8" ###< doctrine/doctrine-bundle ### +###> rabbitmq ### +RABBITMQ_DEFAULT_USER=mbin +RABBITMQ_DEFAULT_PASS=!ChangeThisRabbitPass! +###< rabbitmq ### + ###> symfony/messenger ### # Choose one of the transports below -RABBITMQ_PASSWORD=!ChangeThisRabbitPass! -MESSENGER_TRANSPORT_DSN=amqp://kbin:${RABBITMQ_PASSWORD}@rabbitmq:5672/%2f/messages +MESSENGER_TRANSPORT_DSN=amqp://mbin:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672/%2f/messages #MESSENGER_TRANSPORT_DSN=doctrine://default #MESSENGER_TRANSPORT_DSN=redis://${REDIS_PASSWORD}@${REDIS_HOST}/messages ###< symfony/messenger ### @@ -163,6 +175,8 @@ MERCURE_URL=http://www:80/.well-known/mercure MERCURE_PUBLIC_URL=https://${KBIN_DOMAIN}/.well-known/mercure # The secret used to sign the JWTs MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!" +MERCURE_PUBLISHER_JWT_KEY=${MERCURE_JWT_SECRET} +MERCURE_SUBSCRIBER_JWT_KEY=${MERCURE_JWT_SECRET} ###< symfony/mercure-bundle ### ###> nelmio/cors-bundle ### diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml index ec14b5a65..c10d7767d 100644 --- a/.github/workflows/action.yaml +++ b/.github/workflows/action.yaml @@ -161,7 +161,7 @@ jobs: - name: php-cs-fixer dry-run run: tools/vendor/bin/php-cs-fixer fix --dry-run -v --show-progress=none #--format=checkstyle #would be nice if codeberg did something with this like github does. - build-and-publish-docker-image: + build-and-publish-docker-images: runs-on: ubuntu-latest # Let's only run this on branches and tagged releases only # Because the Docker build takes quite some time. @@ -180,18 +180,36 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker meta data - id: meta + - name: Docker meta data for php image + id: meta_php uses: docker/metadata-action@v5 with: images: ghcr.io/mbinorg/mbin + flavor: suffix=php - - name: Build and push Docker image + - name: Docker meta data for caddy image + id: meta_caddy + uses: docker/metadata-action@v5 + with: + images: ghcr.io/mbinorg/mbin + flavor: suffix=caddy + + - name: Build and push php image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/php/Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta_php.outputs.tags }} + labels: ${{ steps.meta_php.outputs.labels }} + + - name: Build and push caddy image uses: docker/build-push-action@v5 with: context: . - file: ./docker/Dockerfile + file: ./docker/caddy/Dockerfile push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta_caddy.outputs.tags }} + labels: ${{ steps.meta_caddy.outputs.labels }} + # TODO: Integration tests diff --git a/.gitignore b/.gitignore index 0bce5491f..ff3636b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,8 @@ tools/vendor/ .env /public/media/* /public/media -docker/compose.override.yml +compose.override.yml +compose.*.override.yml yarn.lock /metal/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4f770b2b..7150d5c0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,11 @@ With an account on [GitHub](https://github.com) you will be able to [fork this r > [!Note] > If you are a Maintainer with GitHub org admin rights, you do NOT need to fork the project, instead you are allowed to use git branches. See also [C4](C4.md). +### Development + +To get started with development, follow the [bare metal](docs/04-contributing/bare_metal.md) or +[docker](docs/04-contributing/docker.md) guide. + ### Coding Style Guide We use [php-cs-fixer](https://cs.symfony.com/) to automatically fix code style issues according to [Symfony coding standard](https://symfony.com/doc/current/contributing/code/standards.html). diff --git a/compose.dev.yml b/compose.dev.yml new file mode 100644 index 000000000..5ada11640 --- /dev/null +++ b/compose.dev.yml @@ -0,0 +1,76 @@ +services: + www: + volumes: + - caddy_config:/config + - caddy_data:/data + - ./:/var/www/mbin + - ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile + depends_on: + node: + condition: service_completed_successfully + ports: + - 8008:80 + + # Builds the nodejs application + node: + image: "node:22.5.1-alpine3.20" + command: docker/node/build.sh + container_name: node-builder + entrypoint: + - sh + - "-c" + working_dir: /var/www/mbin + depends_on: + php: + condition: service_healthy + networks: + - mbin_external_network + volumes: + - ./:/var/www/mbin/ + + php: + # Builds the Docker image from scratch + build: + target: dev + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./:/var/www/mbin/ + - ./docker/php/conf.d/app.dev.ini:/usr/local/etc/php/conf.d/app.dev.ini + - ./docker/php/conf.d/app.ini:/usr/local/etc/php/conf.d/app.ini + - ./docker/php/entrypoint.sh:/entrypoint.sh + - ./docker/php/php-fpm.d/zz-docker.conf:/usr/local/etc/php-fpm.d/zz-docker.conf + + + messenger: + # Builds the Docker image from scratch + build: + target: dev + command: bin/console -vvvv messenger:consume --all --time-limit=3600 + volumes: + - ./:/var/www/mbin/ + - ./docker/php/entrypoint.sh:/entrypoint.sh + + redis: + volumes: + - redis:/data + + db: + # Allow connecting to the db from the localhost for debugging + ports: + - "127.0.0.1:5432:5432" + volumes: + - postgres:/var/lib/postgresql/data + + rabbitmq: + ports: + - 15672:15672 + volumes: + - rabbitmq:/var/lib/rabbitmq + +volumes: + caddy_config: + caddy_data: + postgres: + rabbitmq: + redis: diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 000000000..39d7e16c2 --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,113 @@ +services: + www: + # To use a prebuilt image from ghcr.io comment out the "image" and "pull_policy" lines + # You can also a tag eg.: "v1.0.0" instead of "latest" + #image: "ghcr.io/mbinorg/mbin:latest-caddy" + #pull_policy: never + ports: + - 8008:80 + volumes: + - ./docker/storage/caddy_config:/config + - ./docker/storage/caddy_data:/data + - ./docker/storage/mbin/public:${MBIN_HOME}/public:ro # Shared volume for php container to write into + environment: + - SERVER_NAME=:80 # the address that the web server binds + - PHP_FASTCGI_HOST=php:9000 # caddy forward traffic to this host via fastcgi + - MERCURE_PUBLISHER_JWT_KEY=$MERCURE_JWT_SECRET + - MERCURE_SUBSCRIBER_JWT_KEY=$MERCURE_JWT_SECRET + depends_on: + - php + + php: + # To use a prebuilt image from ghcr.io comment out the "image" and "pull_policy" lines + # You can also a tag eg.: "v1.0.0" instead of "latest" + #image: "ghcr.io/mbinorg/mbin:latest" + #pull_policy: never + volumes: + - ./docker/storage/mbin:$MBIN_HOME + # If you want to change configs locally, without rebuilding the image use: + # - ../docker/config:/var/www/mbin/config + depends_on: + - redis + - db + - rabbitmq + + messenger: + # To use a prebuilt image from ghcr.io comment out the "image" and "pull_policy" lines + # You can also a tag eg.: "v1.0.0" instead of "latest" + #image: "ghcr.io/mbinorg/mbin:latest" + #pull_policy: never + restart: unless-stopped + command: bin/console -vvvv messenger:consume --all --time-limit=3600 + deploy: + mode: replicated + replicas: 1 # Increase this for better parallelism + healthcheck: + test: ["CMD-SHELL", "ps aux | grep 'messenger[:]consume' || exit 1"] + networks: + - mbin_external_network + volumes: + - ./docker/storage/mbin:$MBIN_HOME + # If you want to change configs locally, without rebuilding the image use: + # - ../docker/config:/var/www/mbin/config + depends_on: + - redis + - db + - rabbitmq + + redis: + image: redis:alpine + container_name: mbin-redis + restart: unless-stopped + networks: + - mbin_external_network + command: /bin/sh -c "redis-server --requirepass $${REDIS_PASSWORD}" + volumes: + - ./docker/storage/redis:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + + db: + image: postgres:${POSTGRES_VERSION:-16}-alpine + container_name: mbin-db + restart: unless-stopped + networks: + - mbin_external_network + volumes: + - ./docker/storage/postgres:/var/lib/postgresql/data + + rabbitmq: + image: rabbitmq:3.13.6-management-alpine + container_name: mbin-rabbitmq + restart: unless-stopped + networks: + - mbin_external_network + volumes: + - ./docker/storage/rabbitmq:/var/lib/rabbitmq + ports: + - 127.0.0.1:15672:15672 + + # Add your favorite reverse proxy (e.g nginx) which accept incoming HTTPS + # traffic and forward to http://www:80 + #nginx: + # image: nginx + # container_name: mbin-nginx + # networks: + # - mbin_external_network + # ports: + # - 443:443 + # volumes: + # - ./docker/nginx.conf:/etc/nginx/nginx.conf + + # Example of a SMTP docker service + # More info: https://hub.docker.com/r/ixdotai/smtp + #mailserver: + # image: ixdotai/smtp:latest + # networks: + # - mbin_external_network + # environment: + # - SMARTHOST_ADDRESS=mail.mysmtp.com + # - SMARTHOST_PORT=587 + # - SMARTHOST_USER=myuser + # - SMARTHOST_PASSWORD=secret + # - SMARTHOST_ALIASES=*.mysmtp.com diff --git a/compose.yml b/compose.yml new file mode 100644 index 000000000..ec70c7d62 --- /dev/null +++ b/compose.yml @@ -0,0 +1,107 @@ +services: + www: + # Builds the Docker image from scratch + build: + dockerfile: docker/caddy/Dockerfile + container_name: mbin-www + restart: unless-stopped + networks: + - mbin_external_network + command: caddy run --config /etc/caddy/Caddyfile --adapter caddyfile + healthcheck: + test: ["CMD-SHELL", "wget -O /dev/null http://localhost:2019/metrics || exit 1"] + env_file: + - .env + depends_on: + - php + + php: + # Builds the Docker image from scratch + build: + dockerfile: docker/php/Dockerfile + container_name: mbin-php + command: php-fpm + working_dir: /var/www/mbin + restart: unless-stopped + networks: + - mbin_external_network + healthcheck: + test: + [ + "CMD-SHELL", + "REQUEST_METHOD=GET SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping cgi-fcgi -bind -connect localhost:9000 || exit 1", + ] + env_file: + - .env + depends_on: + redis: + condition: service_healthy + db: + condition: service_healthy + rabbitmq: + condition: service_healthy + messenger: + # The main application depends on being able to send messages + condition: service_healthy + + messenger: + # Builds the Docker image from scratch + build: + dockerfile: docker/php/Dockerfile + restart: unless-stopped + networks: + - mbin_external_network + command: bin/console messenger:consume --all --time-limit=3600 + working_dir: /var/www/mbin + healthcheck: + test: ["CMD-SHELL", "ps aux | grep 'messenger[:]consume' || exit 1"] + env_file: + - .env + depends_on: + - redis + - db + - rabbitmq + + redis: + image: redis:alpine + container_name: mbin-redis + restart: unless-stopped + networks: + - mbin_external_network + command: /bin/sh -c "redis-server --requirepass $${REDIS_PASSWORD}" + env_file: + - .env + healthcheck: + test: ["CMD", "redis-cli", "ping"] + + db: + image: postgres:${POSTGRES_VERSION:-16}-alpine + container_name: mbin-db + restart: unless-stopped + networks: + - mbin_external_network + env_file: + - .env + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U $POSTGRES_USER" ] + interval: 10s + timeout: 5s + retries: 5 + + rabbitmq: + image: rabbitmq:3.13.6-management-alpine + container_name: mbin-rabbitmq + restart: unless-stopped + networks: + - mbin_external_network + env_file: + - .env + healthcheck: + test: rabbitmq-diagnostics -q ping + interval: 30s + timeout: 30s + retries: 3 + +networks: + mbin_external_network: + diff --git a/docker/.gitignore b/docker/.gitignore index 7376571d1..34ff768e3 100644 --- a/docker/.gitignore +++ b/docker/.gitignore @@ -1 +1 @@ -docker-compose.override.yml +storage/ diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index b48dbaa63..000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,116 +0,0 @@ -FROM php:8.3-fpm-alpine as base - -# Install php extensions, by docker-php-extension-installer -COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ -RUN install-php-extensions amqp bcmath pgsql pdo_pgsql gd curl simplexml dom xml redis intl opcache apcu pcntl exif - -# Install composer -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer - -# Install caddy -RUN apk update && apk add caddy acl fcgi exiftool - -# **Environment variables** -ARG UID=1000 -ENV MBIN_HOME=/var/www/mbin \ - USER=mbin \ - GROUP=www-data - -# Create user -RUN adduser -u $UID -D -g "" $USER $GROUP - -# Create path -RUN mkdir -p $MBIN_HOME && \ - chown -R $USER:$GROUP $MBIN_HOME -WORKDIR $MBIN_HOME - -# PHP configuration (Requires these configuration before "composer install" ) -RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -COPY --link docker/php/conf.d/app.ini $PHP_INI_DIR/conf.d/app.ini -COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/app.prod.or.dev.ini -COPY --link docker/php/conf.d/app.prod.ini $PHP_INI_DIR/conf.d/app.ini-production -COPY --link docker/php/conf.d/app.dev.ini $PHP_INI_DIR/conf.d/app.ini-development -COPY --link docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf -RUN chown -R $USER:$GROUP $PHP_INI_DIR -RUN chown -R $USER:$GROUP /usr/local/etc/php-fpm.d - -#################### - -FROM caddy:2.8.4-builder-alpine AS builder-caddy - -# Build Caddy with the Mercure and Vulcain and brotil cache modules -RUN xcaddy build \ - --with github.com/dunglas/mercure/caddy \ - --with github.com/dunglas/vulcain/caddy \ - --with github.com/ueffel/caddy-brotli - -#################### - -FROM base as builder-composer - -# Composer: install package -COPY composer.* $MBIN_HOME -COPY symfony.lock $MBIN_HOME -ENV COMPOSER_ALLOW_SUPERUSER=1 -RUN composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress - -# Copy repository -COPY --link ./ $MBIN_HOME -RUN cp .env.example_docker .env - -# Dump-autoload and run post-install script -RUN composer dump-autoload --classmap-authoritative --no-dev -RUN composer run-script --no-dev post-install-cmd && \ - chmod +x bin/console && sync - -#################### - -FROM node:22.5.1-alpine3.20 as builder-nodejs - -# Set NodeJS as production by default -ARG NODE_ENV=production -ENV NODE_ENV=${NODE_ENV} - -# Setup environment -ENV MBIN_HOME=/var/www/mbin -RUN mkdir -p $MBIN_HOME -WORKDIR $MBIN_HOME - -# Copy required files -COPY package.json $MBIN_HOME -COPY package-lock.json $MBIN_HOME -COPY --from=builder-composer --link $MBIN_HOME/vendor $MBIN_HOME/vendor - -# NPM: install package -RUN npm ci --include=dev - -# NPM: build (production by default) -COPY --link ./ $MBIN_HOME -RUN npm run build - -#################### - -FROM base as runner - -COPY --chown=$USER:$GROUP --link ./ $MBIN_HOME -RUN cp .env.example_docker .env - -COPY --from=builder-caddy --link /usr/bin/caddy /usr/sbin/caddy -COPY --from=builder-composer --chown=$USER:$GROUP $MBIN_HOME/vendor $MBIN_HOME/vendor -COPY --from=builder-composer --chown=$USER:$GROUP $MBIN_HOME/public/bundles $MBIN_HOME/public/bundles -COPY --from=builder-nodejs --chown=$USER:$GROUP $MBIN_HOME/public $MBIN_HOME/public - -COPY --link docker/caddy/Caddyfile /etc/caddy/Caddyfile -COPY --chmod=755 --link docker/docker-entrypoint ./ - -RUN mkdir -p public/media var/log /data /config && \ - chown -R $USER:$GROUP public/media var /data /config .env && \ - chmod 777 public/media var - -# Switch user -USER $USER:$GROUP - -ENTRYPOINT ["./docker-entrypoint"] - -# Expose port 2019 for caddy metric -EXPOSE 2019/tcp diff --git a/docker/caddy/Caddyfile b/docker/caddy/Caddyfile index 958a2c631..c731841ce 100644 --- a/docker/caddy/Caddyfile +++ b/docker/caddy/Caddyfile @@ -5,7 +5,7 @@ auto_https off } -{$CADDY_EXTRA_CONFIG} +{$CADDY_EXTRA_CONFIG} {$SERVER_NAME} { log @@ -18,7 +18,7 @@ header @static_files Cache-Control max-age=259200 route { - root * /var/www/mbin/public + root * {$MBIN_HOME}/public @supports_webp { header_regexp Accept image/webp diff --git a/docker/caddy/Dockerfile b/docker/caddy/Dockerfile new file mode 100644 index 000000000..c85471608 --- /dev/null +++ b/docker/caddy/Dockerfile @@ -0,0 +1,16 @@ +FROM caddy:2.8.4-builder-alpine AS builder + +# Build Caddy with the Mercure and Vulcain and brotil cache modules +RUN xcaddy build \ + --with github.com/dunglas/mercure/caddy \ + --with github.com/dunglas/vulcain/caddy \ + --with github.com/ueffel/caddy-brotli + + +FROM caddy:2.8.4 as final + +COPY --link docker/caddy/Caddyfile /etc/caddy/Caddyfile +COPY --from=builder /usr/bin/caddy /usr/bin/caddy + +# Expose port 2019 for caddy metric +EXPOSE 2019/tcp diff --git a/docker/compose.prod.yml b/docker/compose.prod.yml deleted file mode 100644 index 210887fa5..000000000 --- a/docker/compose.prod.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: "3.9" - -services: - www: - environment: - - MERCURE_JWT_SECRET=!ChangeThisMercureHubJWTSecretKey! - - redis: - environment: - - REDIS_PASSWORD=!ChangeThisRedisPass! - - db: - environment: - - POSTGRES_PASSWORD=!ChangeThisPostgresPass! - # For backwards compatibility the default PostgreSQL image version is set to v13. - # Feel free to select a newer version of PostgreSQL when starting new. - # Don't forget to also update the .env file accordingly - #- POSTGRES_VERSION=16 - - rabbitmq: - environment: - - RABBITMQ_DEFAULT_PASS=!ChangeThisRabbitPass! - - # Set the following HTTPS variables to TRUE if your environment is using a - # valid certificate behind a reverse proxy. This is likely true for most - # production environments and is required for proper federation, that is, this - # will ensure the webfinger responses include `https:` in the URLs generated. - - php: - environment: - - HTTPS=TRUE - - messenger: - environment: - - HTTPS=TRUE diff --git a/docker/compose.yml b/docker/compose.yml deleted file mode 100644 index bc6bf334c..000000000 --- a/docker/compose.yml +++ /dev/null @@ -1,159 +0,0 @@ -version: "3.9" - -services: - www: - # Builds the Docker image from scratch - build: - context: ../ - dockerfile: docker/Dockerfile - image: mbin - # Or remove the build, context, dockerfile and image lines above and - # use the pre-build image from ghcr.io (you can also a tag eg.: v1.0.0 instead of: latest): - #image: "ghcr.io/mbinorg/mbin:latest" - container_name: mbin-www - restart: unless-stopped - networks: - - mbin_external_network - command: caddy run --config /etc/caddy/Caddyfile --adapter caddyfile - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:2019/metrics || exit 1"] - ports: - - 8008:80 - volumes: - - ./storage/caddy_config:/config - - ./storage/caddy_data:/data - - ./storage/media:/var/www/mbin/public/media - environment: - - SERVER_NAME=:80 # the address that the web server binds - - PHP_FASTCGI_HOST=php:9000 # caddy forward traffic to this host via fastcgi - - MERCURE_PUBLISHER_JWT_KEY=$MERCURE_JWT_SECRET - - MERCURE_SUBSCRIBER_JWT_KEY=$MERCURE_JWT_SECRET - depends_on: - - php - - php: - # Builds the Docker image from scratch - build: - context: ../ - dockerfile: docker/Dockerfile - image: mbin - # Or remove the build, context, dockerfile and image lines above and - # use the pre-build image from ghcr.io (you can also a tag eg.: v1.0.0 instead of: latest): - #image: "ghcr.io/mbinorg/mbin:latest" - container_name: mbin-php - restart: unless-stopped - networks: - - mbin_external_network - command: php-fpm - healthcheck: - test: - [ - "CMD-SHELL", - "REQUEST_METHOD=GET SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping cgi-fcgi -bind -connect localhost:9000 || exit 1", - ] - volumes: - - ./storage/media:/var/www/mbin/public/media - - ./storage/logs:/var/www/mbin/var/log - # If you want to change configs locally, without rebuilding the image use: - # - ../config:/var/www/mbin/config - env_file: - - .env - depends_on: - - redis - - db - - rabbitmq - - messenger: - # Builds the Docker image from scratch - build: - context: ../ - dockerfile: docker/Dockerfile - image: mbin - # Or remove the build, context, dockerfile and image lines above and - # use the pre-build image from ghcr.io (you can also a tag eg.: v1.0.0 instead of: latest): - #image: "ghcr.io/mbinorg/mbin:latest" - restart: unless-stopped - networks: - - mbin_external_network - command: bin/console messenger:consume scheduler_default old async outbox deliver inbox resolve receive failed --time-limit=3600 - healthcheck: - test: ["CMD-SHELL", "ps aux | grep 'messenger[:]consume' || exit 1"] - volumes: - - ./storage/media:/var/www/mbin/public/media - - ./storage/logs:/var/www/mbin/var/log - # If you want to change configs locally, without rebuilding the image use: - # - ../config:/var/www/mbin/config - env_file: - - .env - deploy: - mode: replicated - replicas: 6 - depends_on: - - redis - - db - - rabbitmq - - redis: - image: redis:alpine - container_name: mbin-redis - restart: unless-stopped - networks: - - mbin_external_network - command: /bin/sh -c "redis-server --requirepass $${REDIS_PASSWORD}" - volumes: - - ./storage/redis:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - - db: - image: postgres:${POSTGRES_VERSION:-13}-alpine - container_name: mbin-db - restart: unless-stopped - networks: - - mbin_external_network - volumes: - - ./storage/postgres:/var/lib/postgresql/data - environment: - - POSTGRES_DB=kbin - - POSTGRES_USER=kbin - - rabbitmq: - image: rabbitmq:3.13.6-management-alpine - container_name: mbin-rabbitmq - restart: unless-stopped - networks: - - mbin_external_network - environment: - - RABBITMQ_DEFAULT_USER=kbin - volumes: - - ./storage/rabbitmq:/var/lib/rabbitmq - ports: - - 15672:15672 - - # Add your favorite reverse proxy (e.g nginx) which accept incoming HTTPS - # traffic and forward to http://www:80 - #nginx: - # image: nginx - # container_name: mbin-nginx - # networks: - # - mbin_external_network - # ports: - # - 443:443 - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf - - # Example of a SMTP docker service - # More info: https://hub.docker.com/r/ixdotai/smtp - #mailserver: - # image: ixdotai/smtp:latest - # networks: - # - mbin_external_network - # environment: - # - SMARTHOST_ADDRESS=mail.mysmtp.com - # - SMARTHOST_PORT=587 - # - SMARTHOST_USER=myuser - # - SMARTHOST_PASSWORD=secret - # - SMARTHOST_ALIASES=*.mysmtp.com - -networks: - mbin_external_network: diff --git a/docker/node/build.sh b/docker/node/build.sh new file mode 100755 index 000000000..5d931ef4a --- /dev/null +++ b/docker/node/build.sh @@ -0,0 +1,10 @@ +#/bin/sh +set -e + +if [ -d node_modules ] || [ "$FORCE" != "y" ] ; then + echo "Not rebuilding. Pass env var FORCE=y to rebuild anyway" + exit +fi + +npm ci --include=dev +npm run build diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 000000000..dcc4adf4e --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,159 @@ +FROM php:8.3-fpm-alpine as base +ARG UID=1000 +ARG MBIN_HOME=/var/www/mbin +ENV MBIN_HOME=${MBIN_HOME} +ARG MBIN_USER=mbin +ENV MBIN_USER=${MBIN_USER} +ARG MBIN_GROUP=www-data +ENV MBIN_GROUP=${MBIN_GROUP} + +# Create user +RUN adduser -u $UID -D -g "" $MBIN_USER $MBIN_GROUP +# Install deps +RUN apk update && apk add acl fcgi exiftool + +# Install php extensions, by docker-php-extension-installer +COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ +RUN install-php-extensions \ + amqp \ + apcu \ + bcmath \ + curl \ + dom \ + exif \ + gd \ + intl \ + opcache \ + pcntl \ + pdo_pgsql \ + pgsql \ + redis \ + simplexml \ + xdebug \ + xml + +# Dev will mount the entrypoint +# Prod will embed it +ENTRYPOINT ["/entrypoint.sh"] + + +################################################### +FROM base as dev +# Install composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Switch user to write as non-root into mounted local directories +USER $MBIN_USER:$MBIN_GROUP + + +###################################################### +FROM base as builder-composer +ARG MBIN_SRC=/usr/src/mbin +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +WORKDIR $MBIN_SRC + +# Composer: install package +COPY composer.* ./ +COPY symfony.lock ./ +ENV COMPOSER_ALLOW_SUPERUSER=1 +RUN composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress + +# Copy only required resources +# TODO Use one line once COPY --parents is supported +COPY --link assets ./assets +COPY --link bin ./bin +COPY --link config ./config +COPY --link migrations ./migrations +COPY --link public ./public +COPY --link src ./src +COPY --link templates ./templates +COPY --link translations ./translations + +# Increase memory limit for following steps +RUN echo "memory_limit = 512M" > /usr/local/etc/php/conf.d/memory_limit.ini +# Must have an env file: https://github.com/symfony/symfony/issues/34660 +# Must have all env vars in it https://github.com/symfony/flex/issues/346 +COPY --link .env.example_docker .env + +# Dump-autoload and run post-install script +RUN composer dump-autoload --classmap-authoritative --no-dev +RUN composer run-script --no-dev post-install-cmd && \ + chmod +x bin/console && sync + + +###################################################### +FROM node:22.7.0-alpine3.20 as builder-nodejs +ARG MBIN_SRC=/usr/src/mbin + +# Set NodeJS as production by default +ARG NODE_ENV=production +ENV NODE_ENV=${NODE_ENV} + +# Setup environment +WORKDIR $MBIN_SRC + +# NPM: install package dependencies +COPY package.json . +COPY package-lock.json . +COPY --from=builder-composer --link ${MBIN_SRC}/vendor ./vendor +RUN npm ci --include=dev + +# NPM: build (production by default) +# TODO Use one line once COPY --parents is supported +COPY --link assets/ ./assets/ +COPY --link config/ ./config/ +COPY --link public/ ./public/ +COPY --link src/ ./src/ +COPY --link webpack* ./ +RUN npm run build + + + +################################################### +FROM base as prod +ARG MBIN_SRC=/usr/src/mbin +ENV MBIN_SRC=${MBIN_SRC} + +# rsync for syncing the public/ folder +RUN apk update && apk add rsync + +WORKDIR $MBIN_SRC + +# Copy only necessary resources +# TODO Use one line once COPY --parents is supported +## Folders +COPY --chown=$MBIN_USER:$MBIN_GROUP --link assets/ ./assets +COPY --chown=$MBIN_USER:$MBIN_GROUP --link bin/ ./bin +COPY --chown=$MBIN_USER:$MBIN_GROUP --link config/ ./config +COPY --chown=$MBIN_USER:$MBIN_GROUP --link migrations/ ./migrations +COPY --chown=$MBIN_USER:$MBIN_GROUP --link public/ ./public +COPY --chown=$MBIN_USER:$MBIN_GROUP --link src/ ./src +COPY --chown=$MBIN_USER:$MBIN_GROUP --link templates/ ./templates +COPY --chown=$MBIN_USER:$MBIN_GROUP --link translations/ ./translations + +## Files +COPY --chown=$MBIN_USER:$MBIN_GROUP --link composer* $MBIN_SRC/ +COPY --chown=$MBIN_USER:$MBIN_GROUP --link symfony* $MBIN_SRC/ + +## Build output +COPY --from=builder-composer --chown=$MBIN_USER:$MBIN_GROUP $MBIN_SRC/vendor $MBIN_SRC/vendor +COPY --from=builder-composer --chown=$MBIN_USER:$MBIN_GROUP $MBIN_SRC/public/bundles $MBIN_SRC/public/bundles +COPY --from=builder-nodejs --chown=$MBIN_USER:$MBIN_GROUP $MBIN_SRC/public $MBIN_SRC/public + +# Must have an env file: https://github.com/symfony/symfony/issues/34660 +# Must have all env vars in it https://github.com/symfony/flex/issues/346 +COPY --link .env.example_docker $MBIN_SRC/.env + +# Configure PHP for production +COPY docker/php/conf.d/app.ini /usr/local/etc/php/conf.d/ +COPY docker/php/conf.d/app.prod.ini /usr/local/etc/php/conf.d/ +COPY docker/php/php-fpm.d/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf + +# Empty on first start +# sources from $MBIN_SRC have to be copied into it +WORKDIR $MBIN_HOME + +# Embed entrypoint declared in base image +COPY ./docker/php/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh diff --git a/docker/php/conf.d/app.dev.ini b/docker/php/conf.d/app.dev.ini index 2f486a94d..652cf6b06 100644 --- a/docker/php/conf.d/app.dev.ini +++ b/docker/php/conf.d/app.dev.ini @@ -1,7 +1,10 @@ ; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host ; See https://github.com/docker/for-linux/issues/264 ; The `client_host` below may optionally be replaced with `discover_client_host=yes` -; Add `start_with_request=yes` to start debug session on each request +xdebug.mode=debug xdebug.client_host = 'host.docker.internal' +xdebug.client_port=9000 +; Add start debug session on each request +;xdebug.start_with_request=yes max_execution_time = 120 diff --git a/docker/docker-entrypoint b/docker/php/entrypoint.sh similarity index 55% rename from docker/docker-entrypoint rename to docker/php/entrypoint.sh index 5db1ae18f..48f36b98a 100755 --- a/docker/docker-entrypoint +++ b/docker/php/entrypoint.sh @@ -10,22 +10,24 @@ if [ "$1" == "php-fpm" ] || [ "$1" == "php" ] || [ "$1" == "bin/console" ]; then # if running as a service install assets echo "Starting as service..." - # In production: dump the production PHP config files, - # validate the installed packages (no dev) and dump prod config - if [ "$APP_ENV" == "prod" ]; then - cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" - cp "$PHP_INI_DIR/conf.d/app.ini-production" "$PHP_INI_DIR/conf.d/app.prod.or.dev.ini" - composer install --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress - composer dump-env prod + if [ "$APP_ENV" == "dev" ] ; then + echo "Installing PHP dependencies" + # In development: dump the development PHP config files, + # validate the installed packages (including dev dependencies) and dump dev config + composer install --prefer-dist --no-scripts --no-progress + composer dump-env dev fi - # In development: dump the development PHP config files, - # validate the installed packages (including dev dependencies) and dump dev config - if [ "$APP_ENV" == "dev" ]; then - cp "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" - cp "$PHP_INI_DIR/conf.d/app.ini-development" "$PHP_INI_DIR/conf.d/app.prod.or.dev.ini" - composer install --prefer-dist --no-scripts --no-progress - composer dump-env dev + if [ "$APP_ENV" == "prod" ] ; then + # Parts of mbin are served directly by the webserver without calling php-fpm (public/ folder) + # User uploads and other dynamically created content are inserted into public/ by this container + # The webserver needs access to those newly uploaded files + echo "Syncing mbin src" + rsync \ + --links \ + --recursive \ + --chown $MBIN_USER:$MBIN_GROUP \ + $MBIN_SRC/ $MBIN_HOME fi echo "Waiting for db to be ready..." @@ -65,6 +67,25 @@ if [ "$1" == "php-fpm" ] || [ "$1" == "php" ] || [ "$1" == "bin/console" ]; then echo "ENABLE_ACL is not set!" fi fi + + if [ "$APP_ENV" == "prod" ] ; then + echo "Creating directories and setting ownership" + # Create necessary directories for php-fpm process run by mbin user + mkdir -p public/media var/log /data /config + chown -R $MBIN_USER:$MBIN_GROUP public/media var /data /config .env + chmod 777 public/media var + fi fi -exec "$@" +USER=$(whoami) +if [ "$USER" == "$MBIN_USER" ] ; then + # Probably dev + exec "$@" +else + # Most likely prod + # Run command as non-root user + # Workaround: Allow php-fpm to write to stderr + chown "$MBIN_USER" /proc/self/fd/2 + + exec su "$MBIN_USER" -c "$*" +fi diff --git a/docker/storage/.gitignore b/docker/storage/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/docker/storage/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/docs/02-admin/01-installation/docker.md b/docs/02-admin/01-installation/docker.md index 5596a5fe6..7d564b43f 100644 --- a/docs/02-admin/01-installation/docker.md +++ b/docs/02-admin/01-installation/docker.md @@ -1,18 +1,23 @@ # Docker Installation > [!NOTE] -> Docker installation is currently not advised for production use. Try the [Bare Metal installation](./bare_metal.md) instead. +> Docker installation is currently not advised for production use. Try the [Bare Metal installation](./bare_metal.md) +> instead. + +> [!IMPORTANT] +> If you were already using docker in production, please see the [migration guide](#migration-guide) for updates ## System Requirements - Docker Engine - Docker Compose V2 - > If you are using Compose V1, replace `docker compose` with `docker-compose` in those commands below. +> If you are using Compose V1, replace `docker compose` with `docker-compose` in those commands below. ### Docker Install -The most convenient way to install docker is using an official [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script) +The most convenient way to install docker is using an +official [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script) provided at [get.docker.com](https://get.docker.com/): ```bash @@ -20,7 +25,8 @@ curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh ``` -Alternatively, you can follow the official [Docker install documentation](https://docs.docker.com/engine/install/) for your platform. +Alternatively, you can follow the official [Docker install documentation](https://docs.docker.com/engine/install/) for +your platform. Once Docker is installed on your system, it is recommended to create a `docker` group and add it to your user: @@ -31,82 +37,68 @@ sudo usermod -aG docker $USER ## Mbin Installation -### Preparation - -Clone git repository: +First clone the git repository: ```bash git clone https://github.com/MbinOrg/mbin.git cd mbin ``` -### Docker image preparation +For a test "production" instance, all you have to do is -> [!NOTE] -> If you're using a version of Docker Engine earlier than 23.0, run `export DOCKER_BUILDKIT=1`, prior to building the image. This does not apply to users running Docker Desktop. More info can be found [here](https://docs.docker.com/build/buildkit/#getting-started) +- [Copy example configuration](#copy-the-example-configuration) + - Replace `mbin.domain.tdl` with localhost in the `.env` file +- [Make compose override files](#make-override-files) +- [Start the instance](#start-the-instance) -1. First go to the _docker directory_: +For a full production instance, you'll have to follow the steps below. -```bash -cd docker -``` +### Configuration -2. Use the existing Docker image _OR_ build the docker image. Select one of the two options. +The configuration is held in a `.env` file. It contains information to connect the components to each other (passwords, +service names, ...), where certain things should end up (uploaded files and such), the domain name of your instance, +and more. -#### Build our own Docker image +`docker compose` references it in nearly every service. -If you want to build our own image, run (_no_ need to update the `compose.yml` file): - -```bash -docker build --no-cache -t mbin -f Dockerfile .. -``` - -#### Use Mbin pre-build image - -_OR_ use our pre-build images from [ghcr.io](https://ghcr.io). In this case you need to update the `compose.yml` file: - -```bash -nano compose.yml -``` +#### Copy the example configuration -Find and replace or comment-out the following 4 lines: +`.env.example_docker` contains an example configuration that with very a few tweaks can have you up and running. -```yml -build: - context: ../ - dockerfile: docker/Dockerfile -image: mbin +```shell +cp .env.example_docker .env ``` -And instead use the following line on all places (`www`, `php`, and `messenger` services): - -```yml -image: "ghcr.io/mbinorg/mbin:latest" -``` +#### Configure `.env` -**Important:** Do _NOT_ forget to change **ALL LINES** in that matches `image: mbin` to: `image: "ghcr.io/mbinorg/mbin:latest"` in the `compose.yml` file (should be 4 matches in total). +Below is a table with env variables that are **mandatory** to update -3. Create config files and storage directories: +| Variable | Purpose | +|-----------------------|-------------------------------------------------------------------------------------------------| +| APP_SECRET | Salt passwords and other secrets. Generate a random text at least 16 characters long | +| KBIN_DOMAIN | The postgres user's password | +| KBIN_STORAGE_URL | Where the uploaded media will reachable from externally. See [doc below](#uploaded-media-files) | +| POSTGRES_PASSWORD | The postgres user's password | +| RABBITMQ_DEFAULT_PASS | Used to connect to RabbitMQ | +| REDIS_PASSWORD | Used to connect to Redis | +| SERVER_NAME | Forces server to accept requests only to this domain. **Must have `www:80`** | -```bash -cp ../.env.example_docker .env -cp compose.prod.yml compose.override.yml -mkdir -p storage/media storage/caddy_config storage/caddy_data storage/logs -sudo chown $USER:$USER storage/media storage/caddy_config storage/caddy_data storage/logs -``` +These are optional but recommended to update -### Configure `.env` and `compose.override.yml` +| Variable | Purpose | +|--------------------|---------------------------------------------------| +| MERCURE_JWT_SECRET | Used to connect to the optional mercure service | +| POSTGRES_VERSION | Ensure you're running the latest postgres version | -1. Choose your Redis password, PostgreSQL password, RabbitMQ password, and Mercure password. -2. Place the passwords in the corresponding variables in both `.env` and `compose.override.yml`. -3. Update the `SERVER_NAME`, `KBIN_DOMAIN` and `KBIN_STORAGE_URL` in `.env`. -4. Update `APP_SECRET` in `.env`, generate a new one via: `node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"` -5. _Optionally_: Use a newer PostgreSQL version (current fallback is v13). Update/set the `POSTGRES_VERSION` variable in your `.env` and `compose.override.yml` under `db`. +> [!IMPORTANT] +> Ensure the `HTTPS` environmental variable is set to `TRUE` in `compose.override.yml` for the `php`, `messenger`, +> and `messenger_ap` containers **if your environment is using a valid certificate behind a reverse proxy**. This is +> likely true for most production environments and is required for proper federation, that is, this will ensure the +> webfinger responses include `https:` in the URLs generated. -> [!NOTE] -> Ensure the `HTTPS` environmental variable is set to `TRUE` in `compose.override.yml` for the `php`, `messenger`, and `messenger_ap` containers **if your environment is using a valid certificate behind a reverse proxy**. This is likely true for most production environments and is required for proper federation, that is, this will ensure the webfinger responses include `https:` in the URLs generated. +#### Configure OAuth2 keys (optional) -### Configure OAuth2 keys +OAuth is used by 3rd party app developers. Without these, they will not be able to connect to the server. 1. Create an RSA key pair using OpenSSL: @@ -134,57 +126,104 @@ OAUTH_PASSPHRASE= OAUTH_ENCRYPTION_KEY= ``` -### Running the containers +### Compose override files -By default `docker compose` will execute the `compose.yml` and `compose.override.yml` files. +`docker compose` allows overriding a compose configuration by merging `compose.yml` and `compose.override.yml`. +The latter is ignored by git, which allows you to make modifications to the services without making changes to version +controlled files. -Run the container in the background (`-d` means detach, but this can also be omitted for testing or debugging purposes): +Create a **compose.override.yml** with these contents -```bash -# Go to the docker directory within the git repo -cd docker - -# Starts the containers -docker compose up -d +```yaml +include: + - compose.prod.yml + - compose.prod.override.yml ``` -See your running containers via: `docker ps`. +And an empty `compose.prod.override.yml`. -Then, you should be able to access the new instance via [http://localhost:8008](http://localhost:8008). -You can also access RabbitMQ management UI via [http://localhost:15672](http://localhost:15672). +docker compose will load these files in order and [merge][docker compose merging] sequentially +(last file is most significant): + +- `docker.compose.yml` +- `docker.prod.yml` +- `docker.prod.override.yml` (ignored by git) -### Add auxiliary containers to `compose.yml` +### Docker image preparation -Add any auxiliary container as you want. For example, add a Nginx container as reverse proxy to provide HTTPS encryption. +You have two options for the docker images: -> [!NOTE] -> If you are building the docker images yourself, you might get merge conflicts when changing the `compose.yml` +- [Build your own](#build-our-own-docker-image) +- [Use prebuilt images](#use-mbin-prebuilt-images) -### Uploaded media files +#### Build our own Docker image -Uploaded media files (e.g. photos uploaded by users) will be stored on the host directory `storage/media`. They will be served by the Caddy web server in the `www` container as static files. +> ![WARNING] +> Building your own image will use the code you currently checked out! +> Beware that updates to a running instance might break it. Read the release notes first! -Make sure `KBIN_STORAGE_URL` in your `.env` configuration file is set to be `https://yourdomain.tld/media` (assuming you setup Nginx with SSL certificate by now). +If you want to build our own image, run (_no_ need to update the compose files): -You can also serve those media files on another server by mirroring the files at `storage/media` and changing `KBIN_STORAGE_URL` correspondingly. +```bash +docker compose build --no-cache +``` -### Filesystem ACL support +#### Use Mbin prebuilt images + +There are prebuilt images from [ghcr.io](https://ghcr.io) which can speed up deployment. Should you want to use them +In this case you need to update the `compose.prod.override.yml` file with: + +```yaml +services: + www: + image: "ghcr.io/mbinorg/mbin:latest-caddy" + pull_policy: never + php: + image: "ghcr.io/mbinorg/mbin:latest" + pull_policy: never + messenger: + image: "ghcr.io/mbinorg/mbin:latest" + pull_policy: never +``` -The filesystem ACL is disabled by default, in the `mbin` image. You can set the environment variable `ENABLE_ACL=1` to enable it. Remember that not all filesystems support ACL. This will cause an error if you enable filesystem ACL for such filesystems. +> ![NOTE] +> You can replace `latest` with a version number e.g `1.0.0` -## Run Production +### Running the containers -If you created the file `compose.override.yml` with your configs (`cp compose.prod.yml compose.override.yml`), running production would be the same command: +Run the services in the background (`-d` means detach, but this can also be omitted for testing or debugging purposes): ```bash +# Go to the docker directory within the git repo +cd docker + +# Starts the containers docker compose up -d ``` -**Important:** The docker instance is can be reached at [http://127.0.0.1:8008](http://127.0.0.1:8008), we strongly advise you to put a reverse proxy (like Nginx) in front of the docker instance. Nginx can could listen on ports 80 and 443 and Nginx should handle SSL/TLS offloading. See also Nginx example below. +See your running services via: `docker compose ps`. + +Then, you should be able to access the new instance via [http://localhost](http://localhost:8008). +You can also access RabbitMQ management UI via [http://localhost:15672](http://localhost:15672). + +## Notes + +### Uploaded media files + +Uploaded media files (e.g. photos uploaded by users) will be stored on the host directory `storage/mbin/public/media` +by the `php` container and served by the Caddy web server in the `www` container as static files. + +Make sure `KBIN_STORAGE_URL` in your `.env` configuration file is set to be `https://yourdomain.tld/media` +(assuming you setup Nginx with SSL certificate by now). + +You can also serve those media files on another server by mirroring the files at `storage/mbin/public/media` and +changing `KBIN_STORAGE_URL` correspondingly. + +### Filesystem ACL support -If you want to deploy your app on a cluster of machines, you can -use [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), which is compatible with the provided Compose -files. +The filesystem ACL is disabled by default, in the `mbin` image. You can set the environment variable `ENABLE_ACL=1` to +enable it. Remember that not all filesystems support ACL. This will cause an error if you enable filesystem ACL for such +filesystems. ### Mbin NGINX Server Block @@ -276,3 +315,21 @@ server { } } ``` + +## Migration Guide + +For admins using docker already, as of 2024-08-19, the docker configuration has changed. Read this guide to be sure +you're up to date. The major change is where media files are stored. + +Previously media files were stored at `docker/storage/media`. They will now be stored in `docker/storage/mbin/public/media`. +The easiest way to migrate them is by running these commands as root (`sudo`) + +```shell +mkdir -p docker/storage/www/public +# Copy to have the previous files as a backup +cp -r docker/storage/media docker/storage/www/public +``` + +You should then be set all set. + +[docker compose merging]: https://docs.docker.com/compose/compose-file/13-merge/ diff --git a/docs/04-contributing/development_server.md b/docs/04-contributing/bare_metal.md similarity index 86% rename from docs/04-contributing/development_server.md rename to docs/04-contributing/bare_metal.md index 71debea75..b489a08db 100644 --- a/docs/04-contributing/development_server.md +++ b/docs/04-contributing/bare_metal.md @@ -1,8 +1,8 @@ -# Development Server +# Bare Metal Development Server Requirements: -- PHP v8.2 +- PHP v8.3 - NodeJS - Redis - PostgreSQL @@ -10,13 +10,13 @@ Requirements: --- -- Increase execution time in PHP config file: `/etc/php/8.2/fpm/php.ini`: +- Increase execution time in PHP config file: `/etc/php/8.3/fpm/php.ini`: ```ini max_execution_time = 120 ``` -- Restart the PHP-FPM service: `sudo systemctl restart php8.2-fpm.service` +- Restart the PHP-FPM service: `sudo systemctl restart php8.3-fpm.service` - Connect to PostgreSQL using the postgres user: ```bash @@ -79,4 +79,4 @@ This will give you a minimal working frontend with PostgreSQL setup. Keep in min _Optionally:_ you could also setup RabbitMQ, but the Doctrine messenger configuration will be sufficient for local development. -More info: [Contributing guide](./README.md), [Admin guide](../02-admin/README.md) and [Symfony Local Web Server](https://symfony.com/doc/current/setup/symfony_server.html) +More info: [Contributing guide](../../README.md), [Admin guide](../02-admin/README.md) and [Symfony Local Web Server](https://symfony.com/doc/current/setup/symfony_server.html) diff --git a/docs/04-contributing/docker.md b/docs/04-contributing/docker.md new file mode 100644 index 000000000..c2759ac2a --- /dev/null +++ b/docs/04-contributing/docker.md @@ -0,0 +1,153 @@ +# Docker Development Server + +Using docker can get you up and running quickly as it spins up containers of all the required components. + +## Supported operating systems + + - Linux + +## Requirements + + - [Docker](https://docs.docker.com/get-docker/) + +## Quickstart + +The development environment is run with [docker compose]. + +```shell +# Create a symbolic link to the dev setup that merges and overrides part of compose.yml +ln -nfs compose.dev.yml compose.override.yml +# Create a symbolic link to the configuration for the dev setup +ln -nfs .env.dev_docker .env +# Bring up all the services and detach from the console +docker compose up -d +``` + +Once everything has started, you can navigate to http://localhost:8008. Here's a [docker compose cheatsheet] + +### Alternatives for getting started + +Should you want to edit the `compose.override.yml` file without making changes to `compose.dev.yml`, read further. +Your changes will be ignored by git. + +#### Use the `include` directive + +For docker versions `>= 2.20.0`, you can [include][docker compose include] other YAML files into a compose file. +This has the benefit of keeping up to date with changes to the `compose.dev.yml` without modifying it. + +_compose.override.yml_ +```yaml +include: + - compose.dev.yml + - compose.dev.override.yml # Modifications go into that file +``` + +#### Copy the compose.dev.yml + +Instead of linking, copy the `compose.dev.yml` to `compose.override.yml` and make your changes. + +The downside of this approach is that changes and fixes to `compose.dev.yml` from version control will not reflect +in your compose setup automatically. + +## How it works + +Mbin depends on multiple services (PostgreSQL, Redis, a reverse proxy, PHP, ...). Instead of having one monolithic +docker image that includes these services and all their configs, the services each run in their own containers. + +There's minimal overlap between most services and their `Dockerfile`s. Here are a few things to know. + +### Split compose file + +Instead of having one large compose file to cater to every need, there are multiple - the main one being `compose.yml`. +This takes advantage of [merging][docker compose merging]. Merging is done automatically when a `compose.override.yml` +is present, otherwise the list of files has to be passed with `docker compose -f compose.yml -f file1.yml -f file2.yml`. + +### php/Dockerfile + +[This][php-dockerfile] is the biggest and most complex `Dockerfile` in the repo. +It's a multi-stage file that attempts to make the final image minimal. You can read it, but the most important things +to keep in mind are: + +- the `base` target only has the minimal common items for the next steps +- for production there are builder targets to build the frontend and backend of mbin +- for development, the `dev` target is minimal as the frontend and backend are built when the services are first run +- the final target is `prod` which unites the outputs of the builder targets + +### Building the frontend and backend + +As mentioned [above](#phpdockerfile), the front and backend are built in the `Dockerfile` for production. + +In development, this is taken care of at runtime in [compose.dev.yml]. The `messenger` service is the first PHP +service to run, so it installs PHP dependencies and such. The `node` service is only in the dev compose not production +and requires files from the PHP dependencies to successfully build the frontend. It is thus run afterward. + + +## Frequently executed tasks + +### Modifying behavior with a quick turn-around + +The entire repository is mounted into the `caddy`, `php`, and `messenger` services. + +#### PHP code + +Most PHP code changes are picked up immediately after refreshing the page in the browser. + +#### Other important files + +| file | services | how to refresh | +|---------------|----------------|------------------------------------------------------------------------| +| Caddyfile | caddy | `docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile` | +| entrypoint.sh | php, messenger | `docker compose up -d --no-deps $service` | + + +### Debugging PHP (XDebug) + +PHP supports remote debugging using [XDebug](https://xdebug.org/). This works by hosting an Xdebug server the php +process can call. + +```mermaid +sequenceDiagram + participant Browser + participant rp as Reverse Proxy + participant php as PHP-FPM + participant xdebug as XDebug Host + + Browser->>+rp: http://localhost + rp->>+php: index.php + php->>+xdebug: :9003 + xdebug->>php: break + php-->>xdebug: + xdebug->>php: continue + php-->>xdebug: + xdebug-->>-php: done + php-->>-rp: response + rp-->>-Browser: response +``` + +#### Requirement + +An XDebug server. The XDebug server is often hosted on port 9000 or 9003. Some IDEs (like [PHPStorm]) have it builtin. + +#### Enabling + +Open [app.dev.ini] and uncomment `;xdebug.start_with_request=yes` by removing the `;`, +then restart the `php` service using `docker compose restart php`. + +Once you navigate to a page, your IDE/editor should get called from `php-fpm` within docker. + +#### Special cases + +Firewalls can sometimes get in the way of communication between the docker container and the docker host. + +##### NixOS + +Nixos needs [iptables rules](https://discourse.nixos.org/t/docker-container-not-resolving-to-host/30259/8). + +[app.dev.ini]: ../../docker/php/conf.d/app.dev.ini +[compose.dev.yml]: ../../compose.dev.yml +[docker compose]: https://docs.docker.com/compose/reference/ +[docker compose cheatsheet]: https://devhints.io/docker-compose +[docker compose include]: https://docs.docker.com/compose/compose-file/14-include/ +[docker compose merging]: https://docs.docker.com/compose/compose-file/13-merge/ +[php-dockerfile]: ../../docker/php/Dockerfile +[PHPStorm]: https://www.jetbrains.com/phpstorm/