Skip to content

Instantly share code, notes, and snippets.

@midhunmonachan
Created May 22, 2024 04:35
Show Gist options
  • Save midhunmonachan/3fa9dc5df8f9770fc011653b15e38248 to your computer and use it in GitHub Desktop.
Save midhunmonachan/3fa9dc5df8f9770fc011653b15e38248 to your computer and use it in GitHub Desktop.
This script customizes the Deployer tool for provisioning a Debian 12 server with Nginx and optional SSL setup via Certbot
/**
* This file contains an override for the Deployer provision script to run provision on a Debian 12 server.
* Instead of using the default Caddy server, this code configures the Nginx server.
* Additionally, it also sets up SSL using Certbot.
*
* Read more about Deployer here: https://github.com/deployphp/deployer
*
* @author Midhun Monachan <mail@midhun.ca>
*
*
* How to use:
* Put this file in `deploy` folder in the root directory.
* Then in your delpoy.php file, add `require 'deploy/provision.php';`
*
*/
<?php
namespace Deployer;
use function Deployer\Support\parse_home_dir;
add('recipes', ['provision']);
// Specify which key to copy to server.
// Set to `false` to disable copy of key.
set('ssh_copy_id', '~/.ssh/id_rsa.pub');
set('sudo_password', function () {
return askHiddenResponse(' Password for sudo: ');
});
set('db_type', function () {
$supportedDbTypes = [
'none',
'mysql',
'mariadb',
];
return askChoice(' What DB to install? ', $supportedDbTypes, 0);
});
set('ssl_email', function () {
$domainWithoutSubdomain = implode('.', array_slice(explode('.', get('domain')), -2));
return ask(' Enter an email to use with ssl: ', "admin@$domainWithoutSubdomain");
});
set('ssl_enabled', function () {
return askChoice(' Do you want to configure ssl with certbot', ['yes', 'no'], 0);
});
set('db_name', function () {
return ask(' DB name: ');
});
set('db_user', function () {
return ask(' DB user: ');
});
set('db_password', function () {
return askHiddenResponse(' DB password: ');
});
set('domain', function () {
return ask(' Domain: ');
});
set('php_version', function () {
return ask(' PHP Version: ');
});
desc('Provision the server');
task('provision', [
'provision:check',
'provision:configure',
'provision:pre-requisites',
'provision:repo:php',
'provision:repo:node',
'provision:update',
'provision:upgrade',
'provision:php',
'provision:composer',
'provision:nginx',
'provision:node',
'provision:ssh',
'provision:firewall',
'provision:deployer',
'provision:databases',
'provision:website',
'provision:verify',
]);
desc('Checks pre-required state');
task('provision:check', function () {
if (get('remote_user') !== 'root' && get('become') !== 'root') {
warning('');
warning('Run provision as root: -o remote_user=root');
warning('or with a sudo enabled user: -o become=root');
warning('');
}
$release = run('cat /etc/os-release');
['NAME' => $name, 'VERSION_ID' => $version] = parse_ini_string($release);
if ($name !== 'Debian GNU/Linux' || $version !== '12') {
warning('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
warning('!! !!');
warning('!! Only Debian 12 supported! !!');
warning('!! !!');
warning('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
}
})->oncePerNode();
desc('Collects required params');
task('provision:configure', function () {
$params = [
'sudo_password',
'domain',
'php_version',
'db_type',
'ssl_enabled',
];
$dbparams = [
'db_name',
'db_user',
'db_password',
];
foreach ($params as $name) {
get($name);
}
if (get('db_type') !== 'none') {
foreach ($dbparams as $name) {
get($name);
}
}
if (get('ssl_enabled') === 'yes') {
get('ssl_email');
}
});
desc('Installs Pre-requisites');
task('provision:pre-requisites', function () {
$packages = [
'git',
'unzip',
'ufw',
'whois',
'acl',
'curl',
'gnupg2',
'ca-certificates',
'lsb-release',
'debian-archive-keyring',
'certbot'
];
run('apt-get install -y ' . implode(' ', $packages), ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
})->verbose();
desc('Adds PHP Repository');
task('provision:repo:php', function () {
/*
* PHP
* https://packages.sury.org/php/README.txt
* Last updated: 2024-05-18
*/
run('curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb');
run('dpkg -i /tmp/debsuryorg-archive-keyring.deb');
run('echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list');
})->verbose();
desc('Adds Node.js Repository');
task('provision:repo:node', function () {
/*
* Node.js
* https://github.com/nodesource/distributions
* Last updated: 2024-05-18
*/
run("curl -fsSL https://deb.nodesource.com/setup_22.x | bash -");
})->verbose();
desc('Updates packages list');
task('provision:update', function () {
run('apt-get update -y', ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
})->verbose();
desc('Upgrades distro and all packages');
task('provision:upgrade', function () {
run('apt-get upgrade -y && apt-get dist-upgrade -y', ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
})->verbose();
desc('Setup PHP Packages');
task('provision:php', function () {
$version = get('php_version');
$packages = [
"php$version",
"php$version-cli",
"php$version-fpm",
"php$version-mbstring",
"php$version-pdo",
"php$version-tokenizer",
"php$version-xml",
"php$version-curl",
"php$version-zip",
"php$version-bcmath",
"php$version-mysql",
"php$version-sqlite3",
];
run('apt-get install -y ' . implode(' ', $packages), ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
// Configure PHP-CLI
run("sudo sed -i 's/error_reporting = .*/error_reporting = E_ALL/' /etc/php/$version/cli/php.ini");
run("sudo sed -i 's/display_errors = .*/display_errors = On/' /etc/php/$version/cli/php.ini");
run("sudo sed -i 's/memory_limit = .*/memory_limit = 512M/' /etc/php/$version/cli/php.ini");
run("sudo sed -i 's/upload_max_filesize = .*/upload_max_filesize = 128M/' /etc/php/$version/cli/php.ini");
run("sudo sed -i 's/;date.timezone.*/date.timezone = UTC/' /etc/php/$version/cli/php.ini");
// Configure PHP-FPM
run("sed -i 's/error_reporting = .*/error_reporting = E_ALL/' /etc/php/$version/fpm/php.ini");
run("sed -i 's/display_errors = .*/display_errors = On/' /etc/php/$version/fpm/php.ini");
run("sed -i 's/memory_limit = .*/memory_limit = 512M/' /etc/php/$version/fpm/php.ini");
run("sed -i 's/upload_max_filesize = .*/upload_max_filesize = 128M/' /etc/php/$version/fpm/php.ini");
run("sed -i 's/;date.timezone.*/date.timezone = UTC/' /etc/php/$version/fpm/php.ini");
run("sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php/$version/fpm/php.ini");
// Configure FPM Pool
run("sed -i 's/;request_terminate_timeout = .*/request_terminate_timeout = 60/' /etc/php/$version/fpm/pool.d/www.conf");
run("sed -i 's/;catch_workers_output = .*/catch_workers_output = yes/' /etc/php/$version/fpm/pool.d/www.conf");
run("sed -i 's/;php_flag\[display_errors\] = .*/php_flag[display_errors] = yes/' /etc/php/$version/fpm/pool.d/www.conf");
run("sed -i 's/;php_admin_value\[error_log\] = .*/php_admin_value[error_log] = \/var\/log\/fpm-php.www.log/' /etc/php/$version/fpm/pool.d/www.conf");
run("sed -i 's/;php_admin_flag\[log_errors\] = .*/php_admin_flag[log_errors] = on/' /etc/php/$version/fpm/pool.d/www.conf");
// Configure PHP sessions directory
run('chmod 733 /var/lib/php/sessions');
run('chmod +t /var/lib/php/sessions');
})->verbose();
desc('Installs Composer');
task('provision:composer', function () {
run('curl -sS https://getcomposer.org/installer | php');
run('mv composer.phar /usr/local/bin/composer');
})->verbose();
desc('Setup Nginx Packages');
task('provision:nginx', function () {
$packages = [
'nginx',
'python3-certbot-nginx'
];
run('apt-get install -y ' . implode(' ', $packages), ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
// Fix a bug in nginx package not adding to path and not creating sites-available and sites-enabled
// run('command -v nginx || sudo ln -s /usr/sbin/nginx /usr/bin/nginx');
// run("[ -d /etc/nginx/sites-available/ ] || mkdir -p /etc/nginx/sites-available/");
// run("[ -d /etc/nginx/sites-enabled/ ] || mkdir -p /etc/nginx/sites-enabled/");
})->verbose();
desc('Setup Node.js Packages');
task('provision:node', function () {
$packages = [
'nodejs'
];
run('apt-get install -y ' . implode(' ', $packages), ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
})->verbose();
desc('Configures the ssh');
task('provision:ssh', function () {
run("sed -i 's/PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config");
run('ssh-keygen -A');
run('service ssh restart');
if (test('[ ! -d /root/.ssh ]')) {
run('mkdir -p /root/.ssh');
run('touch /root/.ssh/authorized_keys');
}
})->verbose()->oncePerNode();
desc('Setups a firewall');
task('provision:firewall', function () {
run('ufw allow 22');
run('ufw allow 80');
run('ufw allow 443');
run('ufw --force enable');
})->verbose()->oncePerNode();
desc('Setups a deployer user');
task('provision:deployer', function () {
if (test('id deployer >/dev/null 2>&1')) {
info('deployer user already exist');
} else {
run('useradd deployer');
run('mkdir -p /home/deployer/.ssh');
run('mkdir -p /home/deployer/.deployer');
run('adduser deployer sudo');
run('chsh -s /bin/bash deployer');
run('cp /root/.profile /home/deployer/.profile');
run('cp /root/.bashrc /home/deployer/.bashrc');
// Make color prompt.
run("sed -i 's/#force_color_prompt=yes/force_color_prompt=yes/' /home/deployer/.bashrc");
$password = run("mkpasswd -m sha-512 '%secret%'", ['secret' => get('sudo_password')]);
run("usermod --password '%secret%' deployer", ['secret' => $password]);
if (!empty(get('ssh_copy_id'))) {
$file = parse_home_dir(get('ssh_copy_id'));
if (!file_exists($file)) {
info('Configure path to your public key.');
writeln("");
writeln(" set(<info>'ssh_copy_id'</info>, <info>'~/.ssh/id_rsa.pub'</info>);");
writeln("");
$file = ask(' Specify path to your public ssh key: ', '~/.ssh/id_rsa.pub');
}
run('echo "$KEY" >> /root/.ssh/authorized_keys', ['env' => ['KEY' => file_get_contents(parse_home_dir($file))]]);
}
run('cp /root/.ssh/authorized_keys /home/deployer/.ssh/authorized_keys');
run('ssh-keygen -f /home/deployer/.ssh/id_rsa -t rsa -N ""');
run('chown -R deployer:deployer /home/deployer');
run('chmod -R 755 /home/deployer');
run('chmod 700 /home/deployer/.ssh/id_rsa');
run('usermod -a -G www-data deployer');
run('groups deployer');
}
})->verbose()->oncePerNode();
desc('Provision databases');
task('provision:databases', function () {
$dbType = get('db_type');
if ($dbType === 'none') {
return;
}
invoke('provision:' . $dbType);
})->oncePerNode();
desc('Provision MySQL');
task('provision:mysql', function () {
run('apt-get install -y mysql-server', ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
run("mysql --user=\"root\" -e \"CREATE USER IF NOT EXISTS '{{db_user}}'@'0.0.0.0' IDENTIFIED BY '%secret%';\"", ['secret' => get('db_password')]);
run("mysql --user=\"root\" -e \"CREATE USER IF NOT EXISTS '{{db_user}}'@'%' IDENTIFIED BY '%secret%';\"", ['secret' => get('db_password')]);
run("mysql --user=\"root\" -e \"GRANT ALL PRIVILEGES ON *.* TO '{{db_user}}'@'0.0.0.0' WITH GRANT OPTION;\"");
run("mysql --user=\"root\" -e \"GRANT ALL PRIVILEGES ON *.* TO '{{db_user}}'@'%' WITH GRANT OPTION;\"");
run("mysql --user=\"root\" -e \"FLUSH PRIVILEGES;\"");
run("mysql --user=\"root\" -e \"CREATE DATABASE IF NOT EXISTS {{db_name}} character set UTF8mb4 collate utf8mb4_bin;\"");
})->verbose()->oncePerNode();
desc('Provision MariaDB');
task('provision:mariadb', function () {
run('apt-get install -y mariadb-server', ['env' => ['DEBIAN_FRONTEND' => 'noninteractive'], 'timeout' => 900]);
run("mysql --user=\"root\" -e \"CREATE USER IF NOT EXISTS '{{db_user}}'@'0.0.0.0' IDENTIFIED BY '%secret%';\"", ['secret' => get('db_password')]);
run("mysql --user=\"root\" -e \"CREATE USER IF NOT EXISTS '{{db_user}}'@'%' IDENTIFIED BY '%secret%';\"", ['secret' => get('db_password')]);
run("mysql --user=\"root\" -e \"GRANT ALL PRIVILEGES ON *.* TO '{{db_user}}'@'0.0.0.0' WITH GRANT OPTION;\"");
run("mysql --user=\"root\" -e \"GRANT ALL PRIVILEGES ON *.* TO '{{db_user}}'@'%' WITH GRANT OPTION;\"");
run("mysql --user=\"root\" -e \"FLUSH PRIVILEGES;\"");
run("mysql --user=\"root\" -e \"CREATE DATABASE IF NOT EXISTS {{db_name}} character set UTF8mb4 collate utf8mb4_bin;\"");
})->verbose()->oncePerNode();
desc('Provision website');
task('provision:website', function () {
if (askConfirmation(' Do you want to remove all existing nginx site configs? (Recommended for fresh install)')) {
run('rm -rf /etc/nginx/sites-available/* /etc/nginx/sites-enabled/*');
}
$nginxConfig = parse(<<<EOT
server {
server_name {{domain}};
root {{deploy_path}}/current/public;
index index.html index.htm index.php;
charset utf-8;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
location / {
try_files \$uri \$uri/ /index.php?\$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
error_log /var/log/nginx/{{domain}}-error.log error;
error_page 404 /index.php;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME \$realpath_root\$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT \$realpath_root;
fastcgi_pass unix:/run/php/php{{php_version}}-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
EOT);
if (test("[ -f /etc/nginx/sites-available/{{domain}} ]")) {
if (!askConfirmation('Nginx config already exists. Do you want to overwrite it?')) {
exit;
}
}
run("echo '$nginxConfig' | sudo tee /etc/nginx/sites-available/{{domain}}");
run("sudo ln -sf /etc/nginx/sites-available/{{domain}} /etc/nginx/sites-enabled/");
run("[ -d {{deploy_path}} ] || mkdir -p {{deploy_path}}");
run("chown -R deployer:deployer {{deploy_path}}");
cd('{{deploy_path}}');
run('sudo /usr/sbin/nginx -t');
run('service nginx restart');
run('service php{{php_version}}-fpm restart');
$ssl_enabled = get('ssl_enabled');
if ($ssl_enabled === 'yes') {
run('certbot --nginx -d {{domain}} --non-interactive --agree-tos --email {{ssl_email}}');
run('certbot renew --dry-run');
}
info("Website {{domain}} configured!");
})->verbose()->oncePerNode();
desc('Verifies what provision was successful');
task('provision:verify', function () {
fetch('{{domain}}', 'get', [], null, $info, true);
if ($info['http_code'] === 404) {
info("provisioned successfully!");
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment