No description
  • PHP 61%
  • Twig 33.9%
  • JavaScript 2.6%
  • Jinja 2%
  • CSS 0.3%
  • Other 0.2%
Find a file
2026-06-02 11:50:06 +02:00
backend fix php furl open misstype error 2026-06-02 11:50:06 +02:00
frontend Add platform and install profile fields to SiteType form with EntityType configuration 2026-06-01 16:47:50 +02:00
LICENSE ajout LICENSE 2026-03-06 10:50:08 +02:00
README.md add requirements in README (APG COMPOSER SENDMAIL PYTHON3) 2026-05-29 09:35:15 +02:00

DropFactory

Dropfactory is a solution designed to simplify the deployment and management of multiple Drupal websites. It provides a unique and intuitive interface that allows you to manage your entire portfolio of sites without any coding required. This centralized approach enables teams to focus on content creation and user experience, rather than the technical complexities of managing multiple Drupal installations.

Project Objective

The DropFactory project aims to provide Drupal users with an installation and administration console for low-code site factories. Inspired by the Aegir Hosting System, which we previously used, this project seeks to offer a similarly streamlined experience. It was born from the collaboration between a team specializing in the Drupal CMS and a team with expertise in hosting and managing open-source solutions.

DropFactory is presented as a free and open-source solution. As the initiators of its first release, we invite our partners, friends, and Drupal users to get involved in its development and maintenance. We are recruiting both users and testers. Feel free to participate, contact us, or submit issues on this repository.

Contact Information

The DropFactory project is the result of a collaboration between two partners actively involved in open source communities:

bluedrop.fr SAS - https://bluedrop.fr

Evolix SARL - https://evolix.com

header dropfactory

Install

On a brand new Debian 12/13 system.

Requirements :

  • GNU/Linux Debian 12 (Bookworm) or 13 (Trixie) system dedicated to DropFactory
  • Nginx
  • MariaDB
  • PHP-FPM 8.4 (from deb.sury.org on Debian 12 systems - Debian 13 runs with PHP8.4)
  • Composer - install PHP dependencies for the DropFactory frontend and for Drupal platforms managed by
  • DropFactory. Drupal platforms must have their Composer dependencies installed before sites are created,
  • otherwise commands such as Drush may be unavailable.
  • NodeJS 22 (from NodeSource repositories)
  • ansible (apt install ansible)
  • certbot (apt install certbot)
  • Python 3 PyMySQL module (apt install python3-pymysql or apt install python3-mysqldb)
  • APG password generator (apt install apg)
  • Sendmail-compatible MTA (msmtp-mta, postfix, or equivalent)

System settings :

## Ensure /home is `exec`
# vim /etc/fstab

# systemctl daemon-reload
# mount -o remount /home

## Ensure that created users will have their home directory in 0750
# echo "DIR_MODE=0750" >> /etc/adduser.conf

Create UNIX accounts :

# adduser --disabled-password dropfactory
# adduser --disabled-password --gid $(id --group dropfactory)  www-dropfactory

# adduser www-data dropfactory

Note

: Here, the complete Dropfactory system is deployed inside one UNIX account (with a separate one for PHP execution). It's also possible to deploy to separate UNIX account the frontend and backend sides. Even on a different server as long as they both Use the same MySQL host

Ensure the backend will be able to SSH as root localy :

# su - dropfactory
$ ssh-keygen -t ed25519
$ ^D

# cat ~dropfactory/.ssh/id_ed25519.pub >> .ssh/authorized_keys
# cat << UNILIKELY_EOF >> /etc/ssh/sshd_config.d/zzz-evolinux-custom.conf

Match User root Address 127.0.0.1,::1
    AllowGroups root
    PubkeyAuthentication yes
    PasswordAuthentication no
    PermitRootLogin without-password

UNILIKELY_EOF

# sshd -t
# systemctl reload ssh

# su - dropfactory
$ ssh root@localhost true
$ if [[ $? -eq 0 ]]; then echo "SSH OK"; fi
SSH OK

Fetch the code :

# su - dropfactory
$ git clone https://github.com/Agencebluedrop/DropFactory-Drupal.git dropfactory

Create the databases & their users :

# mysqladmin create dropfactory_backend
# mysqladmin create dropfactory_frontend

# mysql
MariaDB [(none)]> GRANT ALL PRIVILEGES ON dropfactory_backend.* TO 'dropfactory_backend'@'localhost' IDENTIFIED BY 'PASSWORD';
MariaDB [(none)]> GRANT ALL PRIVILEGES ON dropfactory_frontend.* TO 'dropfactory_frontend'@'localhost' IDENTIFIED BY 'PASSWORD';

MariaDB [(none)]> GRANT SELECT on dropfactory_backend.* TO 'dropfactory_frontend'@'localhost';
MariaDB [(none)]> GRANT INSERT on dropfactory_backend.TaskBuffer TO 'dropfactory_frontend'@'localhost';

Note

: Depending on your MariaDB version, the specific grants for dropfactory_frontend user on the table dropfactory_backend.TaskBuffer may not work before creating the table in the database :'(

Configure the DropFactory (SQL Credentials, SSH Keys...) :

### Frontend : Config, vendor install & db migration
# su - dropfactory
$ cd dropfactory/frontend
$ cp -a .env .env.local
$ vim .env.local

$ composer install
$ php bin/console tailwind:build
$ php bin/console importmap:install
$ php bin/console doctrine:migrations:migrate


$ ^D

### Backend : Config, ssh key gen
# su - dropfactory
$ cd dropfactory/backend
$ cp -a conf/config.ini.example conf/config.ini
$ vim conf/config.ini

$ cp -a ansible/vars/main.yml.example ansible/vars/main.yml
$ vim ansible/vars/main.yml

## This key will be your "pull/deploy" for the projects you create (ie: you'll give it read rights on your project repositories)
$ ssh-keygen -t ed25519 -f conf/deploy_key.ed25519

Nginx vhost :

# cat /etc/nginx/sites-available/dropfactory.conf

upstream php_dropfactory {
        server unix:/home/dropfactory/php-fpm.sock;
}

server {
    listen [::]:80;
    listen      80;
    #listen [::]:443 ssl;
    #listen      443 ssl;
    #http2 on;

    server_name dropfactory-sandbox.evolix.eu;

    ## SSL
    #include snippets/letsencrypt.conf;
    #ssl_certificate /etc/letsencrypt/live/dropfactory/fullchain.pem;
    #ssl_certificate_key /etc/letsencrypt/live/dropfactory/privkey.pem;

    ## HTTP to HTTP
    #if ($scheme = http) {
    #        return 301 https://$host$request_uri;
    #}


    root /home/dropfactory/dropfactory/frontend/public;
    index index.htm index.html index.php;

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Very rarely should these ever be accessed outside of your lan
    location ~* \.(txt|log)$ {
        deny all;
    }

    location ~ \..*/.*\.php$ {
        return 403;
    }

    location ~ ^/sites/.*/private/ {
        return 403;
    }

    # Block access to scripts in site files directory
    location ~ ^/sites/[^/]+/files/.*\.php$ {
        deny all;
    }

    # Allow "Well-Known URIs" as per RFC 5785
    location ~* ^/.well-known/ {
        allow all;
    }

    # Block access to "hidden" files and directories whose names begin with a
    # period. This includes directories used by version control systems such
    # as Subversion or Git to store control files.
    location ~ (^|/)\. {
        return 403;
    }

    location / {
        # try_files $uri @rewrite; # For Drupal <= 6
        try_files $uri /index.php?$query_string; # For Drupal >= 7
    }

    location @rewrite {
        rewrite ^/(.*)$ /index.php?q=$1;
    }

    # Don't allow direct access to PHP files in the vendor directory.
    location ~ /vendor/.*\.php$ {
        deny all;
        return 404;
    }

    # In Drupal 8, we must also match new paths where the '.php' appears in
    # the middle, such as update.php/selection. The r
    # and only allows this pattern with the update.php front controller.
    # This allows legacy path aliases in the form of
    # blog/index.php/legacy-path to continue to route to Drupal nodes. If
    # you do not have any paths like that, then you might prefer to use a
    # laxer rule, such as:
    #   location ~ \.php(/|$) {
    # The laxer rule will continue to work if Drupal uses this new URL
    # pattern with front controllers other than update.php in a future
    # release.
    location ~ '\.php$|^/update.php' {
        # Ensure the php file exists. Mitigates CVE-2019-11043
        #try_files $uri =404;
        fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        # Security note: If you're running a version of PHP older than the
        # latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
        # See http://serverfault.com/q/627903/94922 for details.
        include snippets/fastcgi-php.conf;
        # Block httpoxy attacks. See https://httpoxy.org/.
        fastcgi_param HTTP_PROXY "";
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param QUERY_STRING $query_string;
        fastcgi_intercept_errors on;

        fastcgi_pass php_dropfactory;
    }

    # Fighting with Styles? This little gem is amazing.
    # location ~ ^/sites/.*/files/imagecache/ { # For Drupal <= 6
    location ~ ^/sites/.*/files/styles/ { # For Drupal >= 7
        try_files $uri @rewrite;
    }

    # Handle private files through Drupal. Private file's path can come
    # with a language prefix.
    location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
        try_files $uri /index.php?$query_string;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        try_files $uri @rewrite;
        expires max;
        log_not_found off;
    }
    # Enforce clean URLs
    # Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
    # Could be done with 301 for permanent or other redirect codes.
    if ($request_uri ~* "^(.*/)index\.php(.*)") {
        return 307 $1$2;
    }
}

# ln -s /etc/nginx/sites-available/dropfactory.conf /etc/nginx/sites-enabled/
# nginx -t
# systemctl reload nginx

To enable SSL on the vhost, uncomment the include snippets/letsencrypt.conf; directive and add the following file :

# cat /etc/nginx/snippets/letsencrypt.conf
location ~ /.well-known/acme-challenge {
    alias /var/lib/letsencrypt/;
    try_files $uri =404;
    auth_basic off;
    allow all;
}
# certbot certonly --webroot --webroot-path /var/lib/letsencrypt/ -d dropfactory-sandbox.evolix.eu --cert-name dropfactory

You can now uncomment the rest of the SSL related directives & the listen on port 443 (HTTPS)

PHP-FPM :

# cat << UNILIKELY_EOF > /etc/php/8.4/fpm/pool.d/dropfactory.conf
[dropfactory]
user = www-dropfactory
group = dropfactory


listen = /home/dropfactory/php-fpm.sock
listen.owner = dropfactory
listen.group = www-data

pm = ondemand
pm.status_path = /fpm-status
pm.max_children = 20
pm.process_idle_timeout = 10s
pm.max_requests = 1000

UNILIKELY_EOF

# php-fpm8.4 -t
# systemctl reload php8.4-fpm

Enable the backend crontab to treat jobs :

# touch /var/log/dropfactory_output.log
# chown dropfactory:adm /var/log/dropfactory_output.log

# cat /etc/logrotate.d/dropfactory
/var/log/dropfactory_output.log {
        daily
        missingok
        rotate 365
        compress
        delaycompress
        notifempty
        create 0640 dropfactory adm
}


# su - dropfactory

$ crontab -e
*/5 *  *   *   *     cd /home/dropfactory/dropfactory/backend/; date >> /var/log/dropfactory_output.log; php cron.php 2>&1 >> /var/log/dropfactory_output.log