diff --git a/README.md b/README.md index d5fbca8..ea96c0d 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ This repository contains scripts for setting up a very basic web server and adding new sites. It is not perfect, but it will get you off the ground quickly. -This script will install PHP and nginx, and create a +## Installation -## One-liner - -The following command will install these tools. - -It is adviseable to review the contents of `quickstart.sh` before running it, as it's generally a good security practice to understand what a script does before executing it, especially with elevated privileges. You may need to install `curl` first. +These commands will clone the tool onto your server and do the basic installation and configuration of core server software. At the moment that is Nginx, PHP, MySQL, and Certbot. A variety of fail2ban and ufw security measures are also added to improve your security posture. ```bash -curl -sSL https://raw.githubusercontent.com/joby-lol/webserver-config/refs/heads/main/quickstart.sh | bash +# clone the repository and cd into it +git clone https://github.com/joby-lol/webserver-config +cd webserver-config +# run installation script +./install.sh ``` ## Adding a site diff --git a/add-site.sh b/add-site.sh index 9955e02..e9d4eb1 100755 --- a/add-site.sh +++ b/add-site.sh @@ -3,13 +3,27 @@ # Script to add a site to the server if [ "$EUID" -ne 0 ]; then echo "Please run as root or with sudo" - exit + exit 1 fi +# Set up logging +LOG_FILE="/var/log/site_setup.log" +exec > >(tee -a "$LOG_FILE") 2>&1 +echo "Installation started at $(date)" +echo "Logging to $LOG_FILE" + # Prompt for user input read -p "Enter the desired username: " username read -p "Enter the domain name (e.g., example.com): " domain +if [[ ! "$domain" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then + echo "Invalid domain name format" + exit 1 +fi read -p "Enter the email address to be used for this account: " email +if [[ ! "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then + echo "Invalid email format" + exit 1 +fi read -sp "Enter your Cloudflare API key: " cf_api_key echo @@ -19,42 +33,28 @@ mysql_password=$(openssl rand -base64 12) # Get hostname or IP hostname=$(hostname -f) -if [ $? -ne 0 ] || [ -z "$hostname" ] || [[ "$hostname" != *"."* ]]; then - echo "Failed to get a valid hostname. Falling back to IP address." - hostname=$(curl -s -4 icanhazip.com || curl -s -4 ifconfig.me || ip addr show | grep 'inet ' | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 | head -n 1) - if [ -z "$hostname" ]; then - echo "Failed to determine IP address. Using 'localhost' as fallback." - hostname="localhost" - fi -fi - -# Create the user and add to www-data group -sudo useradd -m -s /bin/bash -G www-data $username -echo "$username:$password" | sudo chpasswd # Set up directory structure main_web_root="/var/www/$domain" -sudo mkdir -p $main_web_root/{_main/www,subdomains,logs} +sudo mkdir -p "$main_web_root"/{_main/www,subdomains,logs} + +# Create the user with the web root as home directory and add to www-data and websftpusers groups +sudo useradd -m -d /var/www/$domain -s /usr/sbin/nologin -U -G www-data,websftpusers $username +echo "$username:$password" | sudo chpasswd # Set ownership and permissions for the main site directory -sudo chown -R $username:www-data $main_web_root -sudo find $main_web_root -type d -exec chmod 2750 {} + -sudo find $main_web_root -type f -exec chmod 640 {} + +sudo chown -R "$username:www-data" "$main_web_root" +sudo find "$main_web_root" -type d -exec chmod 2750 {} + +sudo find "$main_web_root" -type f -exec chmod 640 {} + # Set ownership and permissions for the logs directory -sudo chown root:www-data $main_web_root/logs -sudo chmod 755 $main_web_root/logs - -# Ensure new log files get correct permissions -sudo bash -c "echo ' -# Set proper permissions for new log files -umask 022 -' >> /etc/nginx/nginx.conf" +sudo chown root:www-data "$main_web_root/logs" +sudo chmod 755 "$main_web_root/logs" # Create MySQL user and grant permissions sudo mysql < $info_file << EOL +sudo bash -c "cat > \"$info_file\" << EOL Domain: $domain Email: $email @@ -78,114 +78,65 @@ MySQL Username: $username MySQL Password: $mysql_password MySQL Host: $hostname EOL" -sudo chown $username:www-data $info_file -sudo chmod 600 $info_file +sudo chown "$username:$username" "$info_file" +sudo chmod 600 "$info_file" # Create Cloudflare credentials file cf_credentials="$main_web_root/cloudflare.ini" -sudo bash -c "cat > $cf_credentials << EOL +sudo bash -c "cat > \"$cf_credentials\" << EOL dns_cloudflare_api_token = $cf_api_key EOL" -# note that cloudflare credentials are only even readable by root, to make it -# harder for attackers to get them, since they would allow lateral movement -sudo chown root:root $cf_credentials -sudo chmod 600 $cf_credentials +sudo chown root:root "$cf_credentials" +sudo chmod 600 "$cf_credentials" # Request wildcard certificate using Cloudflare DNS challenge sudo certbot certonly --dns-cloudflare \ - --dns-cloudflare-credentials $cf_credentials \ - -d $domain -d *.$domain \ + --dns-cloudflare-credentials "$cf_credentials" \ + -d "$domain" -d "*.$domain" \ --non-interactive \ --agree-tos \ - --email $email + --email "$email" -# Create Nginx configuration +# Copy Nginx configuration from adjacent file nginx_config="/etc/nginx/sites-available/$domain" -sudo bash -c "cat > $nginx_config << EOL -server { - listen 80; - listen [::]:80; - server_name .$domain; - return 301 https://\$host\$request_uri; -} +sudo cp "$(realpath "site-config.conf")" "$nginx_config" -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name .$domain; +# replace $DOMAIN placeholder in the nginx config file +sudo sed -i "s/\$DOMAIN/$domain/g" "$nginx_config" - ssl_certificate /etc/letsencrypt/live/$domain/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/$domain/privkey.pem; - - # Determine the subdomain and set the root accordingly - set \$subdomain ''; - if (\$host ~* ^([^.]+)\.$domain$) { - set \$subdomain \$1; - } - - # Default root for subdomains - root $main_web_root/subdomains/\$subdomain/www; - - # For the main domain, use the _main/www directory - if (\$host = $domain) { - root $main_web_root/_main/www; - access_log $main_web_root/logs/main_access.log; - error_log $main_web_root/logs/main_error.log; - } - - # For subdomains, use separate log files - if (\$subdomain != '') { - access_log $main_web_root/logs/\${subdomain}_access.log; - error_log $main_web_root/logs/\${subdomain}_error.log; - } - - index index.html index.htm index.php; - - location / { - try_files \$uri \$uri/ @router; - } - - location @router { - if (!-f \$document_root/router.php) { - return 404; - } - fastcgi_pass unix:/var/run/php/php-fpm.sock; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME \$document_root/router.php; - } - - location ~ \.php$ { - try_files \$uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:/var/run/php/php-fpm.sock; - fastcgi_index index.php; - include fastcgi_params; - } - - location ~ /\.ht { - deny all; - } -} -EOL" +# replace $MAIN_WEB_ROOT placeholder in the nginx config file +sudo sed -i "s#\$MAIN_WEB_ROOT#$main_web_root#g" "$nginx_config" # Enable the site -sudo ln -s $nginx_config /etc/nginx/sites-enabled/ +sudo ln -sf "$nginx_config" /etc/nginx/sites-enabled/ -# Test Nginx configuration -sudo nginx -t +# Enable log rotation +sudo tee /etc/logrotate.d/"$domain" > /dev/null </dev/null 2>&1 + endscript +} +EOL -# If the test is successful, reload Nginx -if [ $? -eq 0 ]; then - sudo systemctl reload nginx - echo "Nginx configuration has been updated and reloaded." -else - echo "Nginx configuration test failed. Please check the configuration." -fi +# Reload nginx +sudo systemctl reload nginx echo "Setup complete for $domain" -echo "Main website files should be placed in: $main_web_root/_main/www" -echo "Subdomain files should be placed in: $main_web_root/subdomains/[subdomain]/www" -echo "Logs will be stored in: $main_web_root/logs" +echo "Access via SFTP at $hostname with the username $username and the password $password" +echo "Main website files should be placed in: _main/www" +echo "Subdomain files should be placed in: subdomains/[subdomain]/www" echo "Site information (including MySQL credentials) is stored in: $info_file" echo "Cloudflare credentials for this domain are stored in: $cf_credentials" -echo "Remember to log out and log back in for group changes to take effect." +echo "Logs are stored in: logs" diff --git a/fail2ban.conf b/fail2ban.conf deleted file mode 100644 index 53c8d26..0000000 --- a/fail2ban.conf +++ /dev/null @@ -1,31 +0,0 @@ -[sshd] -enabled = true -port = ssh -filter = sshd -logpath = /var/log/auth.log -maxretry = 5 -bantime = 3600 - -[nginx-http-auth] -enabled = true -filter = nginx-http-auth -port = http,https -logpath = /var/log/nginx/error.log -maxretry = 5 -bantime = 3600 - -[mysqld-auth] -enabled = true -filter = mysqld-auth -port = 3306 -logpath = /var/log/mysql/error.log -maxretry = 5 -bantime = 3600 - -[phpmyadmin] -enabled = true -port = http,https -filter = phpmyadmin -logpath = /var/log/nginx/access.log -maxretry = 5 -bantime = 3600 diff --git a/install-server.sh b/install-server.sh deleted file mode 100755 index a8fe1ea..0000000 --- a/install-server.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/bash - -# Core Setup Script for Web Server -if [ "$EUID" -ne 0 ]; then - echo "Please run as root or with sudo" - exit -fi - -# Update and upgrade packages -sudo apt update -sudo apt upgrade -y - -# Install Nginx, UFW, MySQL, and fail2ban -sudo apt install nginx ufw mysql-server fail2ban -y - -# Install PHP and necessary modules -sudo apt install php-fpm php-curl php-gd php-mbstring php-xml php-zip php-pdo php-mysql php-sqlite3 -y - -# UFW Setup -sudo ufw default deny incoming -sudo ufw default allow outgoing -sudo ufw allow ssh -sudo ufw allow 'Nginx Full' -sudo ufw --force enable - -# Swap Setup -sudo fallocate -l 2G /swapfile -sudo chmod 600 /swapfile -sudo mkswap /swapfile -sudo swapon /swapfile - -# Make swap permanent -echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab - -# Adjust swappiness -echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf -sudo sysctl -p - -# Install Certbot and Cloudflare plugin -sudo apt install certbot python3-certbot-dns-cloudflare -y - -# Create post-renewal hook for Nginx reload -sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy -sudo bash -c "cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << EOL -#!/bin/bash -systemctl reload nginx -EOL" -sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh - -# Configure MySQL for Unix socket authentication -sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;" -sudo mysql -e "DELETE FROM mysql.user WHERE User='';" -sudo mysql -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');" -sudo mysql -e "DROP DATABASE IF EXISTS test;" -sudo mysql -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';" -sudo mysql -e "FLUSH PRIVILEGES;" - -# Enable MySQL error logging -sudo sed -i '/^\[mysqld\]/a log_error = /var/log/mysql/error.log' /etc/mysql/mysql.conf.d/mysqld.cnf -sudo systemctl restart mysql - -# Configure fail2ban -sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local - -# Copy custom fail2ban configuration -sudo cp fail2ban.conf /etc/fail2ban/jail.d/custom.conf - -# Ensure fail2ban can read the MySQL log -sudo usermod -a -G adm fail2ban - -# Create MySQL auth filter for fail2ban -sudo bash -c "cat > /etc/fail2ban/filter.d/mysqld-auth.conf << EOL -[Definition] -failregex = ^%(__prefix_line)s[0-9]+ \[Warning\] Access denied for user '\w+'@'' -ignoreregex = -EOL" - -# Create phpMyAdmin filter for fail2ban -sudo bash -c "cat > /etc/fail2ban/filter.d/phpmyadmin.conf << EOL -[Definition] -failregex = ^ .* \"(GET|POST) /phpmyadmin/index\.php HTTP/.*\" 403 - ^ .* \"(GET|POST) /phpmyadmin/.* HTTP/.*\" 404 -ignoreregex = -EOL" - -# Install phpMyAdmin -sudo apt install phpmyadmin -y - -# Configure phpMyAdmin with Nginx -sudo bash -c "cat > /etc/nginx/conf.d/phpmyadmin.conf << EOL -location /phpmyadmin { - root /usr/share/; - index index.php index.html index.htm; - location ~ ^/phpmyadmin/(.+\.php)$ { - try_files \$uri =404; - root /usr/share/; - fastcgi_pass unix:/var/run/php/php-fpm.sock; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; - include /etc/nginx/fastcgi_params; - access_log /var/log/nginx/phpmyadmin_access.log; - } - location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ { - root /usr/share/; - } -} -EOL" - -# Start and enable MySQL -sudo systemctl start mysql -sudo systemctl enable mysql - -# Start and enable PHP-FPM service -sudo systemctl start php-fpm -sudo systemctl enable php-fpm - -# Start and enable Nginx -sudo systemctl start nginx -sudo systemctl enable nginx - -# Start and enable fail2ban -sudo systemctl start fail2ban -sudo systemctl enable fail2ban - -# Output completion messages -echo "Core setup completed successfully!" -echo "Nginx, PHP, MySQL, fail2ban, and phpMyAdmin have been installed and configured" -echo "Certbot and its Cloudflare plugin have been installed" -echo "A post-renewal hook has been added to reload Nginx after certificate renewal" -echo "UFW has been configured and enabled" -echo "A 2GB swap file has been set up and configured" -echo "MySQL has been configured for Unix socket authentication (no password, command-line only for root)" -echo "fail2ban has been configured to protect SSH, Nginx HTTP authentication, and MySQL" -echo "phpMyAdmin has been installed and configured with Nginx" -echo "You can access phpMyAdmin at http://your_server_ip/phpmyadmin" -echo "You can now use 'sudo bash add_site.sh' to add new sites and configure SSL for each" diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..0ab8dec --- /dev/null +++ b/install.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Core Setup Script for Web Server +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Set up logging +LOG_FILE="/var/log/server_setup.log" +exec > >(tee -a "$LOG_FILE") 2>&1 +echo "Installation started at $(date)" + +# run additional scripts from install/ +INSTALL_DIR="./install" + +# Check if install directory exists +if [ ! -d "$INSTALL_DIR" ]; then + echo "Error: $INSTALL_DIR directory not found" + exit 1 +fi + +# Make all .sh files in the install directory executable +find "$INSTALL_DIR" -name "*.sh" -type f -exec chmod +x {} \; + +# Function to run scripts in a directory +run_scripts() { + for script in "$1"/*.sh; do + if [ -f "$script" ]; then + echo "Running $script..." + if [ -z "$SKIP_SCRIPT" ] || [[ "$SKIP_SCRIPT" != *"$(basename "$script")"* ]]; then + bash "$script" + if [ $? -ne 0 ]; then + echo "Error executing $script" + exit 1 + fi + else + echo "Skipping $script" + fi + fi + done +} + +# Run all scripts in the install directory +run_scripts "$INSTALL_DIR" + +# Start and enable key services +for service in mysql nginx fail2ban; do + echo "Restarting $service..." + sudo systemctl restart $service + sudo systemctl enable $service +done + +# Output completion messages +echo "Core setup completed successfully at $(date)" +echo "You can now use add_site.sh to add new users/sites" +echo "For more details, check the log file at $LOG_FILE" diff --git a/install/00-update-install.sh b/install/00-update-install.sh new file mode 100644 index 0000000..af80e36 --- /dev/null +++ b/install/00-update-install.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Update and upgrade packages +apt update +apt upgrade -y + +# Install Nginx, UFW, NTP, and fail2ban +apt install nginx ufw ntp fail2ban -y + +# Install PHP and necessary modules +apt install php-fpm php-curl php-gd php-mbstring php-xml php-zip php-pdo php-mysql php-sqlite3 -y + +# Set timezone +timedatectl set-timezone UTC + +# UFW Setup +ufw default deny incoming +ufw default allow outgoing +ufw allow ssh +ufw allow 'Nginx Full' +ufw --force enable + +# Create custom fail2ban configuration +cat > /etc/fail2ban/jail.d/server.conf << EOL +[sshd] +enabled = true +port = ssh +filter = sshd +logpath = /var/log/auth.log +maxretry = 10 +bantime = 86400 + +[nginx-http-auth] +enabled = true +filter = nginx-http-auth +port = http,https +logpath = /var/log/nginx/error.log +maxretry = 5 +bantime = 3600 +EOL + +# SSH hardening +sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config +sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config +systemctl restart ssh + +# Ensure fail2ban jail.local exists +if [ ! -f /etc/fail2ban/jail.local ]; then + cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local +fi + +# Restart fail2ban to apply changes +systemctl restart fail2ban + +echo "Server setup and fail2ban configuration completed." diff --git a/install/01-nginx-default-root.sh b/install/01-nginx-default-root.sh new file mode 100644 index 0000000..66dd64c --- /dev/null +++ b/install/01-nginx-default-root.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Set up a default nginx site in /var/www/default +echo "Setting up default Nginx site in /var/www/default..." + +# Create the directory for the default site +mkdir -p /var/www/default + +# Create a simple HTML file for the default site +cat > /var/www/default/index.html << EOL + + + + + + Default Site + + +

Nothing here

+

There is nothing here

+ + +EOL + +# Create the Nginx server block configuration for the default site +cat > /etc/nginx/sites-available/default << EOL +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/default; + index index.html; + + server_name _; + + location / { + try_files \$uri \$uri/ =404; + } +} +EOL + +# Enable the default site +ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/ + +# Reload Nginx to apply changes +systemctl reload nginx + +echo "Default Nginx site has been set up and enabled." diff --git a/install/certbot.sh b/install/certbot.sh new file mode 100644 index 0000000..ccacd19 --- /dev/null +++ b/install/certbot.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Install Certbot and Cloudflare plugin +sudo apt install certbot python3-certbot-dns-cloudflare -y + +# Create post-renewal hook for Nginx reload +sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy +sudo bash -c "cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << EOL +#!/bin/bash +systemctl reload nginx +EOL" +sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh \ No newline at end of file diff --git a/install/fail2ban-nginx.sh b/install/fail2ban-nginx.sh new file mode 100644 index 0000000..38a57f5 --- /dev/null +++ b/install/fail2ban-nginx.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +echo "Setting up fail2ban for Nginx errors with strict, moderate, and lenient jails..." + +# Create the filter files +cat > /etc/fail2ban/filter.d/nginx-4xx-strict.conf << EOL +[Definition] +failregex = ^ .* "(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH).*" (401|403) .*$ +ignoreregex = +EOL + +cat > /etc/fail2ban/filter.d/nginx-4xx-moderate.conf << EOL +[Definition] +failregex = ^ .* "(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH).*" (400|405|406|408|413|444) .*$ +ignoreregex = +EOL + +cat > /etc/fail2ban/filter.d/nginx-4xx-lenient.conf << EOL +[Definition] +failregex = ^ .* "(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH).*" (404|429) .*$ +ignoreregex = +EOL + +# Create a new jail configuration file in jail.d +cat > /etc/fail2ban/jail.d/nginx-4xx-jails.conf << EOL +[nginx-4xx-strict] +enabled = true +port = http,https +filter = nginx-4xx-strict +logpath = /var/log/nginx/access.log +maxretry = 10 +findtime = 600 +bantime = 3600 + +[nginx-4xx-moderate] +enabled = true +port = http,https +filter = nginx-4xx-moderate +logpath = /var/log/nginx/access.log +maxretry = 10 +findtime = 600 +bantime = 1800 + +[nginx-4xx-lenient] +enabled = true +port = http,https +filter = nginx-4xx-lenient +logpath = /var/log/nginx/access.log +maxretry = 20 +findtime = 600 +bantime = 900 +EOL + +echo "fail2ban setup for Nginx errors completed with strict, moderate, and lenient jails." diff --git a/install/mysql.sh b/install/mysql.sh new file mode 100644 index 0000000..59a156c --- /dev/null +++ b/install/mysql.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Auto-detect hostname +HOSTNAME=$(hostname -f) + +# Install MySQL and Certbot +apt install mysql-server certbot -y + +# Generate SSL certificate +certbot certonly --webroot -w /var/www/default -d $HOSTNAME --agree-tos --register-unsafely-without-email --non-interactive + +# Configure MySQL for Unix socket authentication +mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;" +mysql -e "DELETE FROM mysql.user WHERE User='';" +mysql -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');" +mysql -e "DROP DATABASE IF EXISTS test;" +mysql -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';" +mysql -e "FLUSH PRIVILEGES;" + +# Allow remote MySQL access (users who can access it this way are added later) +sed -i 's/^bind-address\s*=\s*127\.0\.0\.1/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf + +# Enable MySQL error logging +sed -i '/^\[mysqld\]/a log_error = /var/log/mysql/error.log' /etc/mysql/mysql.conf.d/mysqld.cnf + +# Create a separate file for SSL configuration +cat > /etc/mysql/mysql.conf.d/ssl.cnf << EOL +[mysqld] +# SSL Configuration +ssl-ca=/etc/letsencrypt/live/$HOSTNAME/chain.pem +ssl-cert=/etc/letsencrypt/live/$HOSTNAME/cert.pem +ssl-key=/etc/letsencrypt/live/$HOSTNAME/privkey.pem +require_secure_transport=ON +EOL + +# Restart MySQL to apply changes +systemctl restart mysql + +# Create MySQL fail2ban configuration +cat > /etc/fail2ban/jail.d/mysql.conf << EOL +[mysqld-auth] +enabled = true +filter = mysqld-auth +port = 3306 +logpath = /var/log/mysql/error.log +maxretry = 5 +bantime = 3600 +EOL + +# Ensure fail2ban can read the MySQL log +usermod -a -G adm fail2ban + +# Create MySQL auth filter for fail2ban +cat > /etc/fail2ban/filter.d/mysqld-auth.conf << EOL +[Definition] +failregex = ^%(__prefix_line)s[0-9]+ \[Warning\] Access denied for user '\w+'@'' +ignoreregex = +EOL + +# UFW setup +ufw allow 3306/tcp + +echo "MySQL installation, SSL configuration, and fail2ban setup completed." diff --git a/install/sshd_websftpusers.sh b/install/sshd_websftpusers.sh new file mode 100644 index 0000000..a763e09 --- /dev/null +++ b/install/sshd_websftpusers.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Name of the new group +NEW_GROUP="websftpusers" + +# Create the new group +if ! getent group $NEW_GROUP > /dev/null 2>&1; then + groupadd $NEW_GROUP + echo "Group $NEW_GROUP created successfully." +else + echo "Group $NEW_GROUP already exists." +fi + +# Check if sshd_config.d directory exists, create if it doesn't +SSHD_CONFIG_DIR="/etc/ssh/sshd_config.d" +if [ ! -d "$SSHD_CONFIG_DIR" ]; then + mkdir -p "$SSHD_CONFIG_DIR" + echo "Created $SSHD_CONFIG_DIR directory." + + # Ensure the main sshd_config includes the .d directory + if ! grep -q "Include /etc/ssh/sshd_config.d/\*.conf" /etc/ssh/sshd_config; then + echo "Include /etc/ssh/sshd_config.d/*.conf" >> /etc/ssh/sshd_config + echo "Added include directive to main sshd_config." + fi +fi + +# Create a new configuration file for websftpusers +CONFIG_FILE="$SSHD_CONFIG_DIR/websftpusers.conf" + +cat << EOF > "$CONFIG_FILE" +# Configuration for $NEW_GROUP +Match Group $NEW_GROUP + ChrootDirectory %h + ForceCommand internal-sftp + PasswordAuthentication yes + PermitTunnel no + AllowAgentForwarding no + AllowTcpForwarding no + X11Forwarding no +EOF + +echo "Created $CONFIG_FILE with $NEW_GROUP configuration." + +# Restart SSH service to apply changes +systemctl restart sshd +echo "SSH service restarted to apply changes." + +echo "Setup complete. New group $NEW_GROUP has been created and SSHD configured for SFTP access." diff --git a/install/swap.sh b/install/swap.sh new file mode 100644 index 0000000..3a00aaa --- /dev/null +++ b/install/swap.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Exit if swapfile exists +if [ -f /swapfile ]; then + echo "Swapfile already exists. Exiting..." + exit 1 +fi + +# Swap Setup +sudo fallocate -l 2G /swapfile +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile + +# Make swap permanent +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab + +# Adjust swappiness +echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf +sudo sysctl -p \ No newline at end of file diff --git a/install/unattended-upgrades.sh b/install/unattended-upgrades.sh new file mode 100644 index 0000000..6a26c5f --- /dev/null +++ b/install/unattended-upgrades.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check if script is run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root or with sudo" + exit 1 +fi + +# Exit if unattended-upgrades is already installed +if dpkg -l unattended-upgrades > /dev/null; then + echo "unattended-upgrades is already installed" + exit 0 +fi + +# Install unattended-upgrades +sudo apt install unattended-upgrades apt-listchanges -y + +# Configure unattended-upgrades silently +sudo bash -c "cat > /etc/apt/apt.conf.d/20auto-upgrades << EOL +APT::Periodic::Update-Package-Lists \"1\"; +APT::Periodic::Unattended-Upgrade \"1\"; +APT::Periodic::AutocleanInterval \"7\"; +Unattended-Upgrade::Remove-Unused-Dependencies \"true\"; +Unattended-Upgrade::Automatic-Reboot \"true\"; +EOL" + +# Configure which updates to automatically install +sudo bash -c "cat > /etc/apt/apt.conf.d/50unattended-upgrades << EOL +Unattended-Upgrade::Allowed-Origins { + \"\${distro_id}:\${distro_codename}\"; + \"\${distro_id}:\${distro_codename}-security\"; + \"\${distro_id}ESMApps:\${distro_codename}-apps-security\"; + \"\${distro_id}ESM:\${distro_codename}-infra-security\"; +}; +Unattended-Upgrade::Package-Blacklist { + // Add packages here that you don't want to be automatically upgraded +}; +EOL" diff --git a/quickstart.sh b/quickstart.sh deleted file mode 100755 index 1a286db..0000000 --- a/quickstart.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Script to quickly set up a new server -# Download and install by running: -# curl -sSL https://raw.githubusercontent.com/joby-lol/webserver-config/refs/heads/main/quickstart.sh | bash -if [ "$EUID" -ne 0 ]; then - echo "Please run as root or with sudo" - exit -fi - -# Update package list -sudo apt update - -# Install curl and git -sudo apt install -y git - -# Clone the repository -git clone https://github.com/joby-lol/webserver-config.git - -# Change to the repository directory -cd webserver-config - -# Execute install-server.sh -sudo ./install-server.sh - -echo "Server setup complete. Use 'sudo ./add_site.sh' to add new sites." diff --git a/site-config.conf b/site-config.conf new file mode 100644 index 0000000..2676982 --- /dev/null +++ b/site-config.conf @@ -0,0 +1,85 @@ +server { + listen 80; + listen [::]:80; + server_name .$DOMAIN; + return 301 https://\$host\$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name .$DOMAIN; + + # SSL Configuration + ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + + # Determine the subdomain and set the root accordingly + set \$subdomain ''; + if (\$host ~* ^([^.]+)\.$DOMAIN$) { + set \$subdomain \$1; + } + + # Default root for subdomains + # subdomains are the default, so that we get 404s for nonexistant subdomains + root $MAIN_WEB_ROOT/subdomains/\$subdomain/www; + + # For the main domain, use the _main/www directory + if (\$host = $DOMAIN) { + root $MAIN_WEB_ROOT/_main/www; + } + + # Index file names + index index.html index.htm index.php; + + # Try files first, then use the router.php file if it exists + location / { + try_files \$uri \$uri/ @router; + } + + # Use the router.php file for all nonexistant file requests if it exists + location @router { + if (!-f \$document_root/router.php) { + return 404; + } + fastcgi_pass unix:/var/run/php/php-fpm.sock; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME \$document_root/router.php; + } + + # PHP Configuration + location ~ \.php$ { + try_files \$uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/var/run/php/php-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + } + + # Deny access to .ht* files + location ~ /\.ht { + deny all; + } + + # Additional config options + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + client_max_body_size 20M; + + # Log to both default location and custom site directory, named by subdomain + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + if (\$subdomain != '') { + access_log $MAIN_WEB_ROOT/logs/\$subdomain.access.log; + error_log $MAIN_WEB_ROOT/logs/\$subdomain.error.log; + }else { + access_log $MAIN_WEB_ROOT/logs/_main.access.log; + error_log $MAIN_WEB_ROOT/logs/_main.error.log; + } +} \ No newline at end of file