### ----------------------------------------------------
### -- CLI::Wordpress
### -- Licenses: Artistic-2.0
### -- Authors: librasteve
### -- File: literature/launch.md
### ----------------------------------------------------
# CLI::Wordpress Launch Details

## wordpress-launch.yaml

On ```zef install https://github.com/librasteve/raku-CLI-Wordpress.git```, the file wordpress-launch.yaml is populated. It contains site-specific settings and should be edited to match your domain name, etc before setup is run.

```yaml
#~/.rawp-config/wordpress-launch.yaml
instance:
  domain-name: your_domain
  admin-email: 'admin@your_domain'
  db-image: mysql:8.0
  wordpress-image: wordpress:php8.0-fpm-alpine
  webserver-image: nginx:1.15.12-alpine
  certbot-image: certbot/certbot
  wpcli-image: wordpress:cli-php8.0
  file_uploads: On
  memory_limit: 64M
  upload_max_filesize: 64M
  post_max_size: 64M
  max_execution_time: 600
  client_max_body_size: 64M
```

These settings are then rendered into the nginx-conf, php-conf and docker-compose.yaml files.

_Care should be taken to preserve any changes to this file in the event of a zef reinstall_

## rawp setup, launch & renewal

This sequence largely follows the guidance at viz.
https://www.digitalocean.com/community/tutorials/how-to-install-wordpress-with-docker-compose

The ```rawp setup``` command positions the various configuration files in the ~/wordpress directory and populates them with info from wordpress-launch.yaml.

The ```rawp launch``` command will then start a new Wordpress instance. It performs a 'staging' phase to check your kit for a no ssl website and, if OK, then fetches and installs a letsencrypt ssl certificate and restarts the website.

The ```rawp renewal``` command will then configure the crontab for periodic renewal of the letsencrypt certificate.

## Launch Details

###0. Prerequisites

- server system instance
    - a server running Ubuntu 20.04, along with a non-root user with sudo privileges and an active firewall
    - docker installed on your server
    - docker-compose installed on your server
- registered domain name, DNS records:
    - a record with your_domain -> IP
    - a record with www.your_domain -> IP

###1. Environment Variables
_The ```rawp setup``` command automatically setups config files following these steps._

These secrets are autogenerated during setup to avoid storing them in GitHub.

```yaml
#sudo vi .env [~/.env]
MYSQL_ROOT_PASSWORD='xxx'
MYSQL_USER=yyy
MYSQL_PASSWORD='zzz'
```

Because your .env file contains sensitive information, you want to ensure that it is included in your project’s **.gitignore** and **.dockerignore** files.

```yaml
#sudo vi .dockerignore [~/wordpress/.dockerignore]
.env
.git
.dockerignore
```

###2. Webserver Configuration
_The ```rawp setup``` command automatically setups config files following these steps._

Initially a no ssl config is used for staging...

```apacheconf
#~/wordpress/nginx-conf/nginx.conf
server {
    listen 80;
    listen [::]:80;

    server_name %DOMAIN_NAME% www.%DOMAIN_NAME%;

    index index.php index.html index.htm;

    root /var/www/html;

    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/html;
    }

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location ~ /\.ht {
        deny all;
    }

    location = /favicon.ico {
        log_not_found off; access_log off;
    }
    location = /robots.txt {
        log_not_found off; access_log off; allow all;
    }
    location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        log_not_found off;
    }
}
```

If OK, this is swapped to the ssl version & restarted...
... the line client_max_body_size is rendered according to wordpress-launch.yaml

```apacheconf
#~/wordpress/nginx-conf/nginx.conf
server {
	listen 80;
	listen [::]:80;

	server_name %DOMAIN_NAME% www.%DOMAIN_NAME%;

	location ~ /.well-known/acme-challenge {
		allow all;
		root /var/www/html;
	}

	location / {
		rewrite ^ https://$host$request_uri? permanent;
	}
}

server {
	listen 443 ssl http2;
	listen [::]:443 ssl http2;

	server_name %DOMAIN_NAME% www.%DOMAIN_NAME%;

	index index.php index.html index.htm;

	root /var/www/html;

	server_tokens off;

    ssl_certificate /etc/letsencrypt/live/%DOMAIN_NAME%/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/%DOMAIN_NAME%/privkey.pem;

	include /etc/nginx/conf.d/options-ssl-nginx.conf;

	add_header X-Frame-Options "SAMEORIGIN" always;
	add_header X-XSS-Protection "1; mode=block" always;
	add_header X-Content-Type-Options "nosniff" always;
	add_header Referrer-Policy "no-referrer-when-downgrade" always;
	add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
	# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
	# enable strict transport security only if you understand the implications

    client_max_body_size %CLIENT_MAX_BODY_SIZE%;

	location / {
		try_files $uri $uri/ /index.php$is_args$args;
	}

	location ~ \.php$ {
		try_files $uri =404;
		fastcgi_split_path_info ^(.+\.php)(/.+)$;
		fastcgi_pass wordpress:9000;
		fastcgi_index index.php;
		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_param PATH_INFO $fastcgi_path_info;
	}

	location ~ /\.ht {
		deny all;
	}

	location = /favicon.ico {
		log_not_found off; access_log off;
	}
	location = /robots.txt {
		log_not_found off; access_log off; allow all;
	}
	location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
		expires max;
		log_not_found off;
	}
}

```

###3. Docker Compose Services
_The ```rawp setup``` command automatically setups config files following these steps._

The set of services is predefined by the docker-compose.yaml. The specific docker images used are set in the wordpress-launch.yaml.

```yaml
#`/wordpress/docker-compose.yaml
version: "3"

services:
  db:
    image: %DB-IMAGE%
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MYSQL_DATABASE=wordpress
    volumes:
      - dbdata:/var/lib/mysql
    command: "--default-authentication-plugin=mysql_native_password"
    networks:
      - app-network

  wordpress:
    depends_on:
      - db
    image: %WORDPRESS-IMAGE%
    container_name: wordpress
    restart: unless-stopped
    env_file: .env
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    volumes:
      - wordpress:/var/www/html
      - ./php-conf/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    networks:
      - app-network

  webserver:
    depends_on:
      - wordpress
    image: %WEBSERVER-IMAGE%
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wordpress:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
    networks:
      - app-network

  certbot:
    depends_on:
      - webserver
    image: %CERTBOT-IMAGE%
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - wordpress:/var/www/html

  wpcli:
    depends_on:
      - wordpress
    image: %WPCLI-IMAGE%
    container_name: wpcli
    restart: unless-stopped
    command: tail -f /dev/null
    volumes:
      - wordpress:/var/www/html
    environment:
      - WORDPRESS_DB_HOST=db:3306
      - WORDPRESS_DB_USER=$MYSQL_USER
      - WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD
      - WORDPRESS_DB_NAME=wordpress
    profiles:
      - dev
    networks:
      - app-network
        
  git:
    depends_on:
      - wordpress
    image: ubuntu:latest
    container_name: git
    restart: unless-stopped
    command: tail -f /dev/null
    volumes:
      - wordpress:/var/www/html
    networks:
      - app-network

volumes:
  certbot-etc:
  wordpress:
  dbdata:

networks:
  app-network:
    driver: bridge
```

###4. SSL Certificates and Credentials
_The ```rawp launch``` command automatically setups SSL following these steps._

#### Start No SSL config for staging
```sudo docker-compose up -d```
```sudo docker-compose ps```
```text
Output
  Name                 Command               State           Ports       
-------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0                      
db          docker-entrypoint.sh --def ...   Up       3306/tcp, 33060/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp 
wordpress   docker-entrypoint.sh php-fpm     Up       9000/tcp           
```

#### Run certbot staging command
```yaml
sudo docker-compose run certbot certonly --webroot --webroot-path=/var/www/html --email admin-email --agree-tos --no-eff-email -d your_domain -d www.your_domain --staging
```

#### Check dummy certificate is OK
```sudo docker-compose exec webserver ls -la /etc/letsencrypt/live```
```text
Output
total 16
drwx------    3 root     root          4096 May 10 15:45 .
drwxr-xr-x    9 root     root          4096 May 10 15:45 ..
-rw-r--r--    1 root     root           740 May 10 15:45 README
drwxr-xr-x    2 root     root          4096 May 10 15:45 your_domain
```

#### Run certbot production command
Now that you know your request will be successful, you can edit the certbot service definition to remove the --staging flag.

```yaml
sudo docker-compose run certbot certonly --webroot --webroot-path=/var/www/html --email admin-email --agree-tos --no-eff-email  -d your_domain -d www.your_domain --force-renewal
```

_Note that letsencrypt restricts this to 5 calls per week._

The certbot image is then restarted:
```sudo docker-compose up --force-recreate --no-deps certbot```

###5. Modifying the Web Server Configuration and Service Definition

_The ```rawp launch``` command automatically setups SSL following these steps._

Enabling SSL in your Nginx configuration will involve adding an HTTP redirect to HTTPS, specifying your SSL certificate and key locations, and adding security parameters and headers.

Since you are going to recreate the webserver service to include these additions, you can stop it now: ```sudo docker-compose stop webserver```

Before modifying the configuration file, get the recommended Nginx security parameter from Certbot using curl:
```sudo curl -sSLo nginx-conf/options-ssl-nginx.conf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf```

This command will save these parameters in a file called options-ssl-nginx.conf, located in the nginx-conf directory.

Next, the no SSL nginx configuration file is replaced by the SSL nginx configuration file (see section 2).

Recreate the webserver service: ```sudo docker-compose up -d --force-recreate --no-deps webserver```

###6. Front End Setup

Now you can visit your new server forntend and perform the standard Wordpress configuration steps to select language and setup an admin user.

###7. Certificate Renewals
_The ```rawp renewal``` command automatically sets up letsencrypt SSL certificate renewal following these steps._

Letsencrypt certificates are valid for 90 days. This cron job will renew your certificates and reload your nginx configuration.

The renewal script is predefined at ~/wordpress/ssl_renew.sh:
```bash
#!/bin/bash

DOCKER="/usr/bin/docker"
COMPOSE="/usr/bin/docker-compose"

cd /home/ubuntu/wordpress
$DOCKER container prune -f
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
```

This script is loaded to the server instance crontab with the following lines:
```bash
0 12 * * * /home/ubuntu/wordpress/ssl_renew.sh >> /var/log/cron.log 2>&1
0 */4 * * * date >> /var/log/cron.log 2>&1    #4 hourly timestamp
```

It is then run every 4 hours. You can monitor the status with ```tail -f /var/log/cron.log```
  


