BookStack @SLES16 (openSUSE v16.1) (with Internet access)

Let's deploy BookStack web application to the Internet reachable environment: 
 
 Solution: BookStack ( www , github ) 
 Resources: VM in the laptop lab. 
 Stack:
 
 mbp7dox2: openSUSE 16.1 + nginx + MariaDB 
 
 
 
 Create VM. 
 Preparations: 
 sudo su
zypper install \
 tmux \
 git \
 php \
 php-gd \
 php-zip \
 php-mysqlnd \
 php8-cli \
 php8-phar \
 php8-curl \
 php8-fileinfo \
 php8-mbstring \
 php8-pdo \
 php8-mysql \

 
 Install MariaDB and perform recommended post-install 
 zypper install mariadb-server

systemctl enable --now mariadb
systemctl status mariadb
 
 Secure the fresh setup 
 mariadb-secure-installation
 
 
 Create database and application user 
 export db_pass="Very-Strong-Password123"

mariadb -u root --execute="CREATE DATABASE bookstack;"
mariadb -u root --execute="CREATE USER 'bookstack'@'localhost' IDENTIFIED BY '${db_pass}';"
mariadb -u root --execute="GRANT ALL ON bookstack.* TO 'bookstack'@'localhost'; FLUSH PRIVILEGES;"
 
 Verify freshly created user can login 
 mariadb -u bookstack -p
 
 
 Setup nginx, enable SSL 
 zypper install nginx
systemctl enable --now nginx
systemctl status nginx
ss -ntap | grep nginx
 
 LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=14387,fd=6),("nginx",pid=14386,fd=6))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=14387,fd=7),("nginx",pid=14386,fd=7))
 
 Create of self-signed certificates and Enable SSL 
 We have local running environment (virtualized local VM) and will use self-signed certificate in this scenario), alternatively, point cert and key accordingly. 
 sudo su
export dir="/data/certs/"
export fqdn="dox.$(hostname)"
echo ${fqdn}

mkdir -p ${dir}/${fqdn}/current/
mkdir -p ${dir}/${fqdn}/archive/

openssl req -x509 \
 -nodes \
 -days 365 \
 -newkey rsa:2048 \
 -subj "/C=FI/ST=State/L=City/O=Home/OU=IT/CN=${fqdn}" \
 -out ${dir}/${fqdn}/current/${fqdn}.crt \
 -keyout ${dir}/${fqdn}/current/${fqdn}.key

ls -la ${dir}/${fqdn}/current/
 
 
 Modify Nginx webserver's config 
 Open Nginx's default config file.
SLES has different Nginx's config structure. 
 export fqdn="$(hostname)"
echo ${fqdn}

vi /etc/nginx/vhosts.d/${fqdn}.conf
 
 It is highly recommended to stop using and promote usage of unencrypted HTTP. Do not disable it, but use forwarders instead. 
 Before I used to disable (=comment or remove) "Server { Listen 80; }" definition (upper block) and enable (=uncomment) "Server { Listen 443 ssl http2; }" definition lower block. 
 But later felt in the trap, that some browsers does initiate first connections unencrypted, I have replaced comments with forwarders. Depending on the environment and use-cases. 
 for BookStack like this (using $request_uri variable) 
 

Shortly, config should look like this
```bash
server {
 listen 80;

 server_name mbp7dox2;
 access_log /var/log/nginx/mbp7dox2.access.log;
 error_log /var/log/nginx/mbp7dox2.error.log;

 return 301 https://$host$request_uri;
}

server {
 listen 443 ssl;

 server_name _;
 access_log /var/log/nginx/mbp7dox2.access.log;
 error_log /var/log/nginx/mbp7dox2.error.log;

 root /usr/share/nginx/html;

 ssl_certificate "/data/certs/mbp7dox2/current/mbp7dox2.crt";
 ssl_certificate_key "/data/certs/mbp7dox2/current/mbp7dox2.key";

 ssl_session_cache shared:SSL:1m;
 ssl_session_timeout 10m;
 ssl_ciphers PROFILE=SYSTEM;
 ssl_prefer_server_ciphers on;

}
 
 SElinux 
 ! TODO:fix context
 
 Test config and reload it: 
 nginx -t
nginx -s reload
ss -ntap | grep nginx
 
 
 Test it, requesting to show headers only, '-k' - will skip cert checks, as they will fail for not matching the CN (common name). Using local address to avoid DNS lookup. 
 curl -I https://127.0.0.1 -k
 
 
 Open firewall (create security policies to pass the traffic in) 
 firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
firewall-cmd --list-all
 
 Foundation is done. We can proceed with application setup. 
 Install PHP-FPM 
 zypper install php-fpm

systemctl enable --now php-fpm
systemctl status php-fpm
 
 We need to know, where PHP-FPM is listening, to point requests correctly from webserver below: 
 fgrep -irn listen /etc/php8/fpm/ 
 
 
 port 9000 we shall be using and not local file socket 
 /etc/php8/fpm/php-fpm.d/www.conf:43:listen = 127.0.0.1:9000
 
 Let's see which user and group is owning the php. 
 fgrep -irn user /etc/php8/fpm/ | grep -v \;
fgrep -irn group /etc/php8/fpm/ | grep -v \;
 
 
 these are 
 user=wwwrun
group=www
 
 Let's see which user and group nginx is running with 
 fgrep -irn user /etc/nginx/
ss -ntap | grep nginx
 
 
 -"Aha!". Let's change PHP-FPM be run as nginx 
 vi /etc/php8/fpm/php-fpm.d/www.conf
 
 Should be like this, using nginx user: 
 ; user = wwwrun
user = nginx
; group = www
group = nginx
 
 
 Remember to restart the service 
 systemctl restart php-fpm
 
 Install Composer and make available globally. 
 ref to 
 https://getcomposer.org/download/
 
 as a normal user, not root 
 mkdir -p ~/utils/composer
cd ~/utils/composer/

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

php -r "if (hash_file('sha384', 'composer-setup.php') === 'c8b085408188070d5f52bcfe4ecfbee5f727afa458b2573b8eaaf77b3419b0bf2768dc67c86944da1544f06fa544fd47') { echo 'Installer verified'.PHP_EOL; } else { echo 'Installer corrupt'.PHP_EOL; unlink('composer-setup.php'); exit(1); }"
 
 
 php composer-setup.php

php -r "unlink('composer-setup.php');"

php composer.phar
sudo mv composer.phar /usr/local/bin/composer
 
 
 Prepare home for application 
 Naming convention: application.host, in my case it is. Always bear in mind, that one host may have more than one application. If there are direct FQDN, then use it to make management easier. 
 whoami
sudo su

# privileged user (you)
export pu="anton"

export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}"
echo ${dir}

mkdir -p ${dir}

# permit priviledged user to own app directory
chown -R ${pu}:${pu} ${dir}
exit

# as priviledged user, not as root
export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}"
cd ${dir}
 
 Create application-specific configuration file in the webserver 
 sudo su
export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}"

cat << _EOF_ > /etc/nginx/vhosts.d/${app}.conf
server {
 listen 443 ssl;

 server_name ${app};

 access_log /var/log/nginx/${app}_access.log;
 error_log /var/log/nginx/${app}_error.log;

 ssl_certificate "/data/certs/${app}/current/${app}.crt";
 ssl_certificate_key "/data/certs/${app}/current/${app}.key";

 ssl_protocols TLSv1.2;
 ssl_prefer_server_ciphers on;
 ssl_session_cache shared:SSL:1m;
 ssl_session_timeout 10m;
 ssl_ciphers PROFILE=SYSTEM;

 root ${dir}/BookStack/public;
 index index.php;
 
 client_max_body_size 1G;
 fastcgi_buffers 64 4K;

 location / {
 try_files \$uri \$uri/ /index.php?\$query_string;
 }

 location ~ \.php(?:$|/) {
 fastcgi_split_path_info ^(.+\.php)(/.+)$;
 include fastcgi_params;
 fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
 fastcgi_param PATH_INFO \$fastcgi_path_info;
 fastcgi_pass 127.0.0.1:9000;
 }

 location ~* \.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
 expires 30d;
 access_log off;
 }

 location ~ ^/(?:\.htaccess|data|config|db_structure\.xml|README) {
 deny all;
 }

}
_EOF_
 
 Verify 
 ls -la /etc/nginx/vhosts.d/${app}.conf
 
 Prepare hostnames to match certificates CNs (for local usage) 
 vi /etc/hosts
 
 127.0.0.1 localhost mbp7dox2 dox.mbp7dox2
 
 Test and reload the webserver: 
 nginx -t
nginx -s reload

export host="$(hostname)"
export app="dox.${host}"
echo ${app}

curl https://${app} -k
 
 Installation of BookStack application 
 Change directory before cloning the repo. As a normal user, not root 
 export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}/"

cd ${dir}
pwd
 
 /var/www/dox.lt58dox1
 
 Clone repo 
 git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch
ls -la
 
 
 Build an application 
 cd Bookstack
which composer
composer update
composer install --no-dev
 
 Should be built successfully:
 
 Creating missing directories 
 issue 
 #3 /var/www/dox.lt58dox1/BookStack/vendor/la...; PHP message: PHP Fatal error: Uncaught UnexpectedValueException: The stream or file "/var/www/dox.lt58dox1/BookStack/storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied
 
 sudo su

export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}/"

ls -la ${dir}/BookStack/storage/logs/

# Create an empty log and directory for views
touch ${dir}/BookStack/storage/logs/laravel.log

chown nginx:nginx ${dir}/BookStack/storage/logs/laravel.log
namei -mo ${dir}/BookStack/storage/logs/laravel.log
 
 
 Configure application 
 Copy template, generate salt and configure .env 
 sudo su

export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}"
cd ${dir}/BookStack/
pwd

cp .env.example .env
php artisan key:generate
 
 Are you sure you want to run this command? Yes [Enter] 
 vi .env
 
 Should be simple as 
 APP_KEY=base64:goKeJWQsFFboBGOSF5+eti2Yv1auP4rXvxVbQ4Iupgc=
APP_URL=https://dox.lt58dox1

DB_HOST=localhost
DB_DATABASE=bookstack
DB_USERNAME=bookstack
DB_PASSWORD=Very-Strong-Password123

# not in use
MAIL_DRIVER=smtp
MAIL_FROM_NAME="BookStack"
MAIL_FROM=bookstack@example.com
MAIL_HOST=localhost
MAIL_PORT=587
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
 
 Create database schema 
 php artisan migrate
 
 
 Set least necessary permissions for application 
 export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}"

# not inside app dir, but level up (not to be affected by itself)
vi fixperm-${app}.sh
 
 #!/bin/bash

export host="$(hostname)"
export app="dox.${host}"
export dir="/var/www/${app}/BookStack/"

# priviledged user
export pu="anton"

# webserver's user
export wsu="nginx"

chown -R ${wsu}:${pu} ${dir}
subdirs=("" "/storage/" "/bootstrap/cache/" "/public/uploads/")
for subdir in "${subdirs[@]}" ; do
 echo "--[ subdir: ${subdir} ]--"
 chown -R ${wsu}:${pu} ${dir}/${subdir}
done 
for subdir in "${subdirs[@]}" ; do
 echo "--[ subdir: ${subdir} ]--"
 find ${dir}/${subdir} -type d -exec chmod 0770 {} \+
 find ${dir}/${subdir} -type f -exec chmod 0660 {} \+
done 
 
 chmod +x ./fixperm.sh
./fixperm.sh
 
 
 Application is ready 
 
 Used space: 
 Minimal openSUSE16.1 + webserver + PHP-FPM + MariaDB 
 df -hT | sort
 
 
 date
export dir="/var/www/${app}"
df -hT ${dir}
du -h ${dir} --max-depth=1