Created
May 22, 2024 04:35
-
-
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 bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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