Skip to content

Instantly share code, notes, and snippets.

@oliverthiele
Last active May 22, 2024 19:09
Show Gist options
  • Save oliverthiele/999b29a675a0c46687b627dc259bf382 to your computer and use it in GitHub Desktop.
Save oliverthiele/999b29a675a0c46687b627dc259bf382 to your computer and use it in GitHub Desktop.
Script for fully automated composer-based installation of TYPO3 (v12.4 / v11.5) on a clean Ubuntu 22.04 or 24.04 root server with nginx, certbot, brotli, webp, ...
#!/bin/bash
# Exit on error
set -e
### Before executing this script make a system update:
# $> apt update; apt --assume-yes dist-upgrade; apt --assume-yes autoremove;
# $> reboot
# VirtualBox does not work with ipv6, so you have to disable it before running this script:
# $> sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
# $> sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
# Variables
wwwRoot='/var/www/'
composerDirectory="${wwwRoot}typo3/"
typo3PublicDirectory="${composerDirectory}public/"
typo3Version='^12.4'
# Trap to clean up on exit
trap 'echo "Script exited with error. Cleaning up..."; cleanup' EXIT
cleanup() {
# Implement cleanup logic if needed
echo "Cleanup complete."
}
############# Edit begin: ##################
setVariables() {
# For later use with TYPO3 v12 + v13
# echo "Select the TYPO3 version to be installed
# 1) v12
# 2) v13"
# read -p 'Option: ' typo3Option
#
# case ${typo3Option} in
# 1) typo3Version='^12.4' ;;
# 2) typo3Version='^13.4' ;;
# *)
# echo "Invalid option!"
# exit 1
# ;;
# esac
echo "TYPO3 Version ${typo3Version}"
if [[ "${typo3Version}" = '^12.4' || "${typo3Version}" = '^13.4' ]]; then
pathSettings="${composerDirectory}config/system/"
pathAdditionalSettings="${pathSettings}additional.php"
typo3CliName='typo3'
else
pathSettings="${composerDirectory}public/typo3conf/"
pathAdditionalSettings="${pathSettings}AdditionalConfiguration.php"
typo3CliName='typo3cms'
fi
systemPass=$(generatePassword)
echo "System Password: ${systemPass}"
}
############# Edit end: ##################
getUbuntuVersionAndSetDefaultVariables() {
ubuntuVersion=$(lsb_release -rs)
echo "Ubuntu: ${ubuntuVersion}"
# todo Install php 7.4 for TYPO3 10
case "${ubuntuVersion}" in
'24.04') phpVersion='8.3' ;;
'22.04') phpVersion='8.1' ;;
'20.04') phpVersion='7.4' ;;
'18.04') phpVersion='7.2' ;;
*)
echo "Unsupported Ubuntu version!"
exit 1
;;
esac
pathToPhpIni="/etc/php/${phpVersion}/fpm/php.ini"
echo "PHP Version: ${phpVersion}"
echo "Path to php.ini: ${pathToPhpIni}"
}
confirmInstallation() {
read -rp "Install TYPO3 in '${composerDirectory}' with PHP ${phpVersion}. Is this correct [y/N] " response
case "$response" in
[yY][eE][sS] | [yY])
echo "Start the installation"
;;
*)
echo "Installation cancelled by user. Exiting..."
exit
;;
esac
}
installDependencies() {
echo "INFO Install necessary build dependencies"
apt update
apt install -y build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libssl-dev wget git libbrotli-dev
}
installSoftware() {
echo "INFO Install System (nginx, php ${phpVersion}, MySQL, Redis, …)"
apt --assume-yes install nginx-full apache2-utils \
php${phpVersion}-gd php${phpVersion}-mysql \
php-soap php-apcu php-redis \
php${phpVersion}-{fpm,cli,common,curl,zip,gd,mysql,xml,mbstring,intl,yaml,opcache} \
redis-server mariadb-server \
graphicsmagick ghostscript git tig zip unzip catdoc argon2 file zsh zsh-syntax-highlighting \
dos2unix jq webp brotli \
update-notifier-common
if [[ "${ubuntuVersion}" =~ ^20.04$|^22.04$|^24.04$ ]]; then
installCertbot
fi
}
installAdditionalSoftware() {
local monitResponseRegex='^([yY][eE][sS]|[yY])$'
read -rp "Do you want to install monit? [y/N] " response
if [[ "$response" =~ $monitResponseRegex ]]; then
## Install MTA, if monit is installed
apt --assume-yes install monit
fi
}
installCertbot() {
echo "Install Lets Encrypt certbot"
apt --assume-yes install certbot python3-certbot-nginx
}
############# Create DB ###################
createDatabase() {
encryptionKey="$(openssl rand -hex 48)"
if [ ! -f .env.temp ]; then
echo "INFO Create MySQL DB"
databaseUser='typo3'
# create random password
databasePassword=$(generatePassword)
databaseName=${databaseUser}_1
databaseHost='localhost'
mysql -e "CREATE DATABASE ${databaseName} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER ${databaseUser}@localhost IDENTIFIED BY '${databasePassword}';"
mysql -e "GRANT ALL PRIVILEGES ON ${databaseName}.* TO '${databaseUser}'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"
cat >.env.temp <<EOL
DB_DB="${databaseName}"
DB_USER="${databaseUser}"
DB_PASS="${databasePassword}"
DB_HOST="${databaseHost}"
ENCRYPTION_KEY="${encryptionKey}"
EOL
fi
}
cleanTargetDirectoryAndDatabase() {
if [ -d "${composerDirectory}" ]; then
read -rp "The directory ${composerDirectory} already exists. Are you sure you want to delete it? The database ${databaseName} will be dropped, too! [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
rm -rf ${composerDirectory}
echo "Directory ${composerDirectory} removed"
mysql -e "DROP DATABASE ${databaseName};"
echo "Database ${databaseName} dropped"
else
echo "Operation cancelled"
fi
fi
}
#########################
# Optimize php.ini
# #######################
optimizePhpSettings() {
echo "Optimize PHP ${phpVersion} settings in ${pathToPhpIni}"
# sed -i 's/;opcache.revalidate_freq=2/opcache.revalidate_freq=300/g' /etc/php-7.0.d/10-opcache.ini
sed -i 's/max_execution_time = 30/max_execution_time = 240/' ${pathToPhpIni}
sed -i 's/max_input_time = 60/max_input_time = 120/' ${pathToPhpIni}
# PHP 7.2
sed -i 's/; max_input_vars = 1000/max_input_vars = 10000/' ${pathToPhpIni}
# PHP 7.4
sed -i 's/;max_input_vars = 1000/max_input_vars = 10000/' ${pathToPhpIni}
sed -i 's/memory_limit = 128M/memory_limit = 256/' ${pathToPhpIni}
sed -i 's/post_max_size = 8M/post_max_size = 200M/' ${pathToPhpIni}
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 200M/' ${pathToPhpIni}
sed -i 's/max_file_uploads = 20/max_file_uploads = 200/' ${pathToPhpIni}
service php${phpVersion}-fpm restart
}
################################################## Install composer
installComposer() {
echo "Install composer from https://getcomposer.org"
EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then
echo >&2 'ERROR: Invalid installer checksum'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
if [ $RESULT -eq 0 ]; then
echo 'Composer installation was successful'
else
echo 'Composer Setup Result:' $RESULT
fi
# Make composer globally availible
mv composer.phar /usr/local/bin/composer
}
################################################## Install Yarn
installYarn() {
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
apt update
apt --assume-yes install yarn
}
################################################## Install TYPO3
installTypo3() {
echo "INFO Install TYPO3"
chown www-data:www-data /var/www/ -R
cd ${wwwRoot} || exit
sudo -i -H -u www-data composer create-project "typo3/cms-base-distribution:${typo3Version}" /var/www/typo3/
# Add Extension directory for your extensions, sitepackages, ...
cd ${composerDirectory} || exit
mkdir packages
sudo -i -H -u www-data composer --file="${composerDirectory}composer.json" config repositories.local '{"type": "path", "url": "./packages/*"}'
sudo -u www-data sh -c "cd ${composerDirectory} && composer require vlucas/phpdotenv"
sudo -u www-data sh -c "composer require typo3/cms-adminpanel ${typo3Version} typo3/cms-lowlevel ${typo3Version} \
typo3/cms-redirects ${typo3Version} typo3/cms-recycler ${typo3Version} typo3/cms-workspaces ${typo3Version} \
typo3/cms-linkvalidator ${typo3Version} typo3/cms-reports ${typo3Version} typo3/cms-opendocs ${typo3Version} \
typo3/cms-scheduler ${typo3Version}"
sudo -u www-data sh -c "composer require plan2net/webp"
# Add dev packages
sudo -u www-data sh -c "composer require --dev typo3/coding-standards ssch/typo3-rector"
# Setup / add
# .php-cs-fixer.dist.php
# .editorconfig
sudo -u www-data sh -c "composer exec typo3-coding-standards setup project"
# todo Check versions for TYPO3 v10, v11 + v12
# Check versions for TYPO3 v10, v11 + v12
if [[ "${typo3Version}" != '^13.4' ]]; then
sudo -u www-data sh -c "composer require helhum/typo3-console"
fi
find -type d -print0 | xargs -0 chmod 2770 && find -type f ! -perm /u=x,g=x,o=x -print0 | xargs -0 chmod 0660
chown www-data: /var/www/ -R
php ${composerDirectory}vendor/bin/${typo3CliName} install:setup \
--no-interaction \
--database-user-name=${databaseUser} \
--database-user-password=${databasePassword} \
--database-host-name=${databaseHost} \
--database-port=3306 \
--database-name=${databaseName} \
--use-existing-database \
--admin-user-name=TYPO3-Admin \
--admin-password=${systemPass} \
--site-setup-type=site
if test -f "${composerDirectory}vendor/bin/typo3cms"; then
echo "alias typo3cms='${composerDirectory}/vendor/bin/typo3cms'" >>/var/www/.zshrc
fi
if test -f "${composerDirectory}vendor/bin/typo3"; then
echo "alias typo3='${composerDirectory}/vendor/bin/typo3'" >>/var/www/.zshrc
fi
}
getNginxVersion() {
nginxVersion=$(nginx -v 2>&1 | grep -oP '(?<=nginx/)[0-9.]+')
echo "INFO Detected Nginx version: ${nginxVersion}"
}
downloadNginxSource() {
echo "INFO Downloading Nginx source for version ${nginxVersion}"
cd /usr/local/src || exit
wget "https://nginx.org/download/nginx-${nginxVersion}.tar.gz"
tar -zxvf "nginx-${nginxVersion}.tar.gz"
git clone https://github.com/google/ngx_brotli.git
cd ngx_brotli || exit
git submodule update --init
}
compileNginxWithBrotli() {
echo "INFO Compiling Nginx with Brotli module for version ${nginxVersion}"
cd /usr/local/src/nginx-${nginxVersion} || exit
./configure --with-compat --add-dynamic-module=../ngx_brotli
make modules
echo "INFO Copying Brotli modules to Nginx modules directory"
cp objs/ngx_http_brotli_filter_module.so /usr/share/nginx/modules/
cp objs/ngx_http_brotli_static_module.so /usr/share/nginx/modules/
chmod 644 /usr/share/nginx/modules/ngx_http_brotli_*
}
configureBrotliInNginx() {
echo "INFO Configuring Brotli in Nginx"
# Ensure the modules directory exists
mkdir -p /etc/nginx/modules
# Add the Brotli module loading to the main nginx.conf
if ! grep -q "load_module modules/ngx_http_brotli_filter_module.so;" /etc/nginx/nginx.conf; then
sed -i '1iload_module modules/ngx_http_brotli_filter_module.so;' /etc/nginx/nginx.conf
fi
if ! grep -q "load_module modules/ngx_http_brotli_static_module.so;" /etc/nginx/nginx.conf; then
sed -i '1iload_module modules/ngx_http_brotli_static_module.so;' /etc/nginx/nginx.conf
fi
}
configureNginx() {
################################################## Enable Website in nginx
echo "INFO Configure website in nginx"
# Enable server_tokens off in nginx.conf
sed -i 's/# server_tokens off;/server_tokens off;/' /etc/nginx/nginx.conf
# Set secure SSL protocols
sed -i 's/ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;/ssl_protocols TLSv1.2 TLSv1.3;/' /etc/nginx/nginx.conf
# Brotli compression settings
cat >/etc/nginx/conf.d/brotli.conf <<EOL
# Brotli configuration
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon image/svg+xml application/x-font-ttf font/opentype;
# Next row already set in /etc/nginx/nginx.conf
# gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/x-font-ttf font/opentype image/svg+xml image/x-icon;
EOL
cat >/etc/nginx/conf.d/webp.conf <<EOL
# /etc/nginx/conf.d/webp.conf
# WebP
# https://centminmod.com/webp/
# https://packagist.org/packages/plan2net/webp
map \$http_accept \$webpok {
default 0;
"~*webp" 1;
}
map \$http_cf_cache_status \$iscf {
default 1;
"" 0;
}
map \$webpok\$iscf \$webp_suffix {
11 "";
10 ".webp";
01 "";
00 "";
}
EOL
cat >/etc/nginx/snippets/browserCaching.nginx <<EOL
# CSS / JS
location ~* ^/typo3temp/Assets/.*\.js {
expires max;
add_header Vary Accept-Encoding;
add_header Pragma public;
add_header Cache-Control "public";
gzip on;
}
location ~* ^/typo3conf/ext/.*\.(js|css)$ {
expires max;
add_header Pragma public;
add_header Cache-Control "public";
}
# Media
location ~* \.(?:ico|gif|jpe?g|png|ogg|bmp|png|webp|mp4|webm|h264|h265|svg|woff|woff2|ttf|eot)$ {
if (\$http_origin ~ "^(https://code.jquery.com|http://example.com)$") {
add_header Access-Control-Allow-Headers Content-Type;
add_header Access-Control-Max-Age 86400;
add_header Access-Control-Allow-Origin \$http_origin;
}
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
# # etag is supported on nginx >= 1.3.3
# # etag on;
# # https://www.maxcdn.com/blog/accept-encoding-its-vary-important/
# add_header Vary Accept-Encoding;
}
EOL
# cat >/etc/nginx/snippets/compression.nginx <<EOL
## Compression
#gzip on;
#gzip_http_version 1.1;
#gzip_min_length 1000;
#gzip_buffers 16 8k;
#gzip_disable "MSIE [1-6] \.";
#gzip_types
# # text/html # text/html is always compressed by HttpGzipModule
# text/css
# text/xml
# application/x-javascript
# application/atom+xml
# text/mathml
# text/plain
# text/vnd.sun.j2me.app-descriptor
# text/vnd.wap.wml
# text/x-component
# text/javascript
# application/javascript
# application/json
# application/xml
# application/rss+xml
# font/truetype
# font/opentype
# application/vnd.ms-fontobject
# image/svg+xml;
#gzip_vary on;
#EOL
if [ -f "/etc/nginx/sites-available/default" ]; then
rm /etc/nginx/sites-available/default
fi
if [ -L "/etc/nginx/sites-enabled/default" ]; then
rm /etc/nginx/sites-enabled/default
fi
#cat >/etc/nginx/sites-available/default <<EOL
#server {
# listen 80 default_server;
# listen [::]:80 default_server;
# location / {
# deny all;
# }
#}
#EOL
cat >/etc/nginx/snippets/typo3v11Up.nginx <<EOL
# TYPO3 11.5 Backend URLs
location = /typo3 {
rewrite ^ /typo3/;
}
# Allow access to all public resources
location ~ ^/typo3/(.*/)?Resources/Public/ {
allow all;
break;
}
location /typo3/ {
# include snippets/BasicAuth.nginx;
try_files \$uri /typo3/index.php\$is_args\$args;
}
EOL
cat >/etc/nginx/sites-available/typo3.nginx <<EOL
server {
listen 80;
listen [::]:80;
charset utf-8;
root ${typo3PublicDirectory};
# Add index.php to the list if you are using PHP
index index.html index.php;
server_name _;
port_in_redirect off;
server_name_in_redirect off;
client_max_body_size 64M;
client_header_buffer_size 32k;
large_client_header_buffers 16 512k;
include /etc/nginx/conf.d/brotli.conf;
# todo check function
# include snippets/browserCaching.nginx;
# include snippets/compression.nginx;
include snippets/typo3v11Up.nginx;
# Installtool
# Path for TYPO3 7.6: /typo3/sysext/install/Start/Install.php
rewrite ^/typo3/install/\$ /typo3/install.php permanent;
# versionNumberInFilename
rewrite "^(.*)\.(\d{10})\.(css|js)$" \$1.\$3 last;
location / {
# auth_basic "Restricted";
# auth_basic_user_file /var/www/typo3/.htpasswd;
# any / all
# satisfy any;
# allow 127.0.0.1;
# allow 127.0.1.1;
try_files \$uri \$uri/ /index.php?\$args;
}
# For WebP Extension
location ~* ^.+\.(png|gif|jpe?g)$ {
add_header Vary "Accept";
add_header Cache-Control "public, no-transform";
try_files \$uri\$webp_suffix \$uri =404;
}
location = /favicon.ico {
log_not_found off;
access_log off;
expires max;
break;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Restrict access to deleted files in Recycler directories
location ~ ^/fileadmin/(.*/)?_recycler_/ {
deny all;
access_log off;
log_not_found off;
break;
}
# For CSS with compression
location ~* "\.css(\.|\.\d{10}\.)gzip$" {
rewrite "^(.+css)\.(\d+\.)gzip$" /\$1.gzip;
add_header Content-Encoding gzip;
add_header Vary Accept-Encoding;
add_header Access-Control-Allow-Origin *;
gzip off;
types { text/css gzip; }
expires max;
log_not_found off;
}
# For JavaScript with compression
location ~* "\.js(\.|\.\d{10}\.)gzip$" {
rewrite "^(.+js)\.(\d{10}\.)gzip$" /\$1.gzip;
add_header Content-Encoding gzip;
add_header Vary Accept-Encoding;
gzip off;
default_type application/javascript;
expires max;
log_not_found off;
}
# pass PHP scripts to FastCGI server
location ~ \.php$ {
# regex to split \$uri to \$fastcgi_script_name and \$fastcgi_path
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Check that the PHP script exists before passing it
try_files \$fastcgi_script_name =404;
# Bypass the fact that try_files resets \$fastcgi_path_info
# see: http://trac.nginx.org/nginx/ticket/321
set \$path_info \$fastcgi_path_info;
fastcgi_param PATH_INFO \$path_info;
fastcgi_index index.php;
include fastcgi.conf;
fastcgi_param TYPO3_CONTEXT Development;
#fastcgi_param TYPO3_CONTEXT Production/Staging;
#fastcgi_param TYPO3_CONTEXT Production;
fastcgi_pass unix:/var/run/php/php${phpVersion}-fpm.sock;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
location ~ /\.ht {
deny all;
}
}
EOL
ln -sfT /etc/nginx/sites-available/typo3.nginx /etc/nginx/sites-enabled/typo3.nginx
service nginx restart
}
activateTypo3() {
################################################## Enable TYPO3 installation
echo "Enable TYPO3 installation"
cd ${typo3PublicDirectory} || exit
touch FIRST_INSTALL
cat >/var/www/typo3/.env.example <<EOL
PROJECT_NAME=""
# Domain without scheme (used for trustedHostPattern)
DOMAIN=""
DB_DB=""
DB_USER=""
DB_PASS=""
DB_HOST="localhost"
ENCRYPTION_KEY=""
TYPO3_INSTALL_TOOL=""
SMTP_SERVER=""
SMTP_USER=""
SMTP_PASSWORD=""
# Bool: 0 / 1
SMTP_TRANSPORT_ENCRYPT=0
DEFAULT_MAIL_FROM_ADDRESS=""
DEFAULT_MAIL_FROM_NAME=""
EOL
cp -ap /root/.env.temp /var/www/typo3/.env
cp -ap /root/.env.temp /var/www/typo3/.env.development
mkdir -p ${pathSettings}
cat >${pathAdditionalSettings} <<EOL
<?php
declare(strict_types=1);
defined('TYPO3') or die();
use TYPO3\CMS\Core\Core\Environment;
\$context = Environment::getContext();
\$envFile = '.env';
if (\$context->isDevelopment()) {
\$envFile = '.env.development';
}
\$envPath = dirname(__DIR__, 2);
/**
* Default for production systems
*/
\$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = '0';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'] = '4096';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] = '2770';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] = '0660';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem'] = 'en_US.UTF-8';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemLocale'] = 'en_US.UTF-8';
/**
* Production environment
* TYPO3_CONTEXT Production
*/
if (\$context->isProduction() && \$context->__toString() === 'Production') {
\$dotenv = Dotenv\Dotenv::createImmutable('/var/www/typo3/', '.env');
\$dotenv->load();
\$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = '0';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'] = '4096';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[Production] TYPO3 CMS';
}
/**
* Development environment
* TYPO3_CONTEXT Development
*/
if (\$context->isDevelopment()) {
\$dotenv = Dotenv\Dotenv::createImmutable('/var/www/typo3/', '.env.development');
\$dotenv->load();
\$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] = '1';
\$GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] = '1';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = '1';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'] = '12290';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[Dev] TYPO3 CMS';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = '.*';
}
/**
* Staging environment
* TYPO3_CONTEXT Production/Staging
*/
if (\$context->isProduction() && \$context->__toString()
=== 'Production/Staging') {
\$dotenv = Dotenv\Dotenv::createImmutable('/var/www/typo3/', '.env.staging');
\$dotenv->load();
\$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['FE']['debug'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = '0';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'] = '4096';
\$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] = '[Staging] TYPO3 CMS';
}
\$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'] = \$_ENV['DB_DB'];
\$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'] = \$_ENV['DB_USER'];
\$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'] = \$_ENV['DB_PASS'];
\$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'] = \$_ENV['DB_HOST'];
\$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_username'] = \$_ENV['SMTP_USER'];
\$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_password'] = \$_ENV['SMTP_PASSWORD'];
\$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = \$_ENV['SMTP_SERVER'];
\$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_encrypt'] = (bool)\$_ENV['SMTP_TRANSPORT_ENCRYPT'];
if (getenv('IS_DDEV_PROJECT') === 'true') {
\$GLOBALS['TYPO3_CONF_VARS'] = array_replace_recursive(
\$GLOBALS['TYPO3_CONF_VARS'],
[
'DB' => [
'Connections' => [
'Default' => [
'dbname' => 'db',
'driver' => 'mysqli',
'host' => 'db',
'password' => 'db',
'port' => '3306',
'user' => 'db',
'charset' => 'utf8mb4',
'tableoptions' => [
'charset' => 'utf8mb4',
'collate' => 'utf8mb4_unicode_ci',
],
],
],
],
// This GFX configuration allows processing by installed ImageMagick 6
'GFX' => [
'processor' => 'ImageMagick',
'processor_path' => '/usr/bin/',
'processor_path_lzw' => '/usr/bin/',
],
// This mail configuration sends all emails to mailpit
'MAIL' => [
'transport' => 'smtp',
'transport_smtp_encrypt' => false,
'transport_smtp_server' => 'localhost:1025',
],
'SYS' => [
'trustedHostsPattern' => '.*.*',
'devIPmask' => '*',
'displayErrors' => 1,
'systemLocale' => 'C.UTF-8'
],
]
);
}
if (\$_ENV['TYPO3_INSTALL_TOOL'] === '') {
echo 'Error: No install tool password given.';
die();
}
EOL
}
################################################## Edit user www-data / add user typo3
configureWwwUser() {
echo "www-data:${systemPass}" | chpasswd
if [ ! -d "/var/www/.ssh/" ]; then
mkdir /var/www/.ssh/
cp -ap /root/.ssh/authorized_keys /var/www/.ssh/authorized_keys
fi
}
################################################## Change all permissions
setPermissions() {
cd ${composerDirectory} || exit
echo "Change permissions"
find -type d -print0 | xargs -0 chmod 2770 && find -type f ! -perm /u=x,g=x,o=x -print0 | xargs -0 chmod 0660
chown www-data: /var/www/ -R
# Permissions for special files
chown -h www-data: /var/www/.ssh/authorized_keys
chmod 0700 /var/www/.ssh/
chmod 0600 /var/www/.ssh/authorized_keys
chmod +x ${composerDirectory}vendor/typo3/cms-cli/typo3
# Old typo3cms file
typo3cmsFile=${composerDirectory}vendor/helhum/typo3-console/typo3cms
if test -f "$typo3cmsFile"; then
chmod +x $typo3cmsFile
fi
}
##############
finish() {
ipAddress=$(ip -4 addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo "---------------------------------------"
echo "---- FINISH ----"
echo "---------------------------------------"
echo ""
echo "TYPO3 User: TYPO3-Admin"
echo "TYPO3 Password / SSH password (www-data): ${systemPass}"
echo "Database Password: ${databasePassword}"
echo ""
echo "You find these passwords in the file ${composerDirectory}install-log-please-remove.log"
echo "Please finish the installation in your browser http://${ipAddress}"
cat >${composerDirectory}install-log-please-remove.log <<EOL
# TYPO3 Server
## System User (SSH):
User: www-data / TYPO3-Admin
Password: ${systemPass}
## Database:
Database: ${databaseName}
User: ${databaseUser}
Password: ${databasePassword}
EOL
chown www-data: ${composerDirectory}install-log-please-remove.log
}
activateZshShell() {
# Change the login shell for user root and www-data
chsh -s /bin/zsh root
if [ ! -d "/root/.oh-my-zsh" ]; then
wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh
sed -i 's/ZSH_THEME="robbyrussell"/ZSH_THEME="agnoster"/g' /root/.zshrc
fi
if ! grep -q "source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ~/.zshrc; then
echo "source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" >>/root/.zshrc
fi
# Configure Zsh plugins to remove git
configureZshPlugins
chsh -s /bin/zsh www-data
cp -ap /root/.oh-my-zsh /root/.zshrc /var/www/
echo "cd /var/www/typo3/" >>/var/www/.zshrc
chown www-data /var/www/ -R
}
configureZshPlugins() {
# Pfad zur .zshrc-Datei von www-data
zshrcPath="/root/.zshrc"
# Sicherstellen, dass die .zshrc-Datei existiert
if [ -f "$zshrcPath" ]; then
# Entferne das git Plugin aus der plugins-Zeile
sed -i 's/plugins=(\(.*\)git\(.*\))/plugins=(\1\2)/' "$zshrcPath"
# Entferne doppelte Leerzeichen, falls vorhanden
sed -i 's/ / /g' "$zshrcPath"
else
echo "$zshrcPath not found."
fi
}
installNode() {
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
echo 'export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/root/.zshrc
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"
nvm install 22
nvm use 22
}
installNodeForWwwData() {
sudo -u www-data -i bash <<EOF
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
echo 'export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>$HOME/.zshrc
export NVM_DIR="\$HOME/.nvm"
[ -s "\$NVM_DIR/nvm.sh" ] && . "\$NVM_DIR/nvm.sh"
[ -s "\$NVM_DIR/bash_completion" ] && . "\$NVM_DIR/bash_completion"
nvm install 22
nvm use 22
EOF
}
generatePassword() {
local CHARS_UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
local CHARS_LOWER='abcdefghijklmnopqrstuvwxyz'
local CHARS_DIGIT='0123456789'
local CHARS_SPECIAL='+-*/!@#$%_&*[]()'
local password=""
pick_char() {
echo -n "${1:RANDOM%${#1}:1}"
}
password=$(pick_char "$CHARS_UPPER")
password+=$(pick_char "$CHARS_LOWER")
password+=$(pick_char "$CHARS_DIGIT")
password+=$(pick_char "$CHARS_SPECIAL")
CHARS_ALL="$CHARS_UPPER$CHARS_LOWER$CHARS_DIGIT$CHARS_SPECIAL"
for ((i = ${#password}; i < 16; i++)); do
password+=$(pick_char "$CHARS_ALL")
done
echo "$(echo "$password" | fold -w1 | shuf | tr -d '\n')"
}
###
# Main body of script starts here
###
echo "==============================================================="
getUbuntuVersionAndSetDefaultVariables
setVariables
confirmInstallation
cleanTargetDirectoryAndDatabase
installDependencies
installSoftware
installComposer
locale-gen de_DE.UTF-8
activateZshShell
createDatabase
optimizePhpSettings
installTypo3
getNginxVersion
downloadNginxSource
compileNginxWithBrotli
configureBrotliInNginx
configureNginx
activateTypo3
configureWwwUser
setPermissions
## For my Frontend-Build
# I use now npm/webpack for my frontend build, so i disabled the yarn installation
# installYarn
# Install node for root
# installNode
# Install node for www-data
installNodeForWwwData
# Monit is optional and needs more configuration
installAdditionalSoftware
finish
echo "End of script..."
trap - EXIT
cleanup
@noldeni
Copy link

noldeni commented Mar 30, 2020

Line 17 should be
# typo3Version='^9.5';
without the double quotes.
Then the installation of 9.5 works.

@oliverthiele
Copy link
Author

Todo: I have add this to my.cnf:

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

@oliverthiele
Copy link
Author

Current version is WIP, but you can install now the complete TYPO3 10.4 on a Ubuntu 20.04. After installation with this script a login in the TYPO3 Backend is possible. Now the script generates .env files and the AdditionalConfiguration.php.

@oliverthiele
Copy link
Author

The current version now works with TYPO3 v12.4 LTS (can be switched to v11.5), but is still WIP. Monit e.g. can be installed but is not configured. New: it installs the zsh shell for root and www-data.

@oliverthiele
Copy link
Author

The following features have been added with the latest update:

  • The script now works with Ubuntu 24
  • Passwords are generated more securely
  • Support for Brotli compression in nginx
  • WebP extension with matching nginx configuration
  • nvm / node / npm is available for www-data (e.g. for webpack frontend builds)
  • yarn is no longer installed by default

Not everything is perfect yet, but it is already functional with these changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment