broke install into many separate scripts
This commit is contained in:
parent
9ded453635
commit
a5a4b85c49
15 changed files with 600 additions and 317 deletions
14
README.md
14
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
|
||||
|
||||
|
|
185
add-site.sh
185
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 <<EOF
|
||||
CREATE USER '${username}'@'localhost' IDENTIFIED BY '${mysql_password}';
|
||||
CREATE USER '${username}'@'%' IDENTIFIED BY '${mysql_password}';
|
||||
CREATE USER IF NOT EXISTS '${username}'@'localhost' IDENTIFIED BY '${mysql_password}';
|
||||
CREATE USER IF NOT EXISTS '${username}'@'%' IDENTIFIED BY '${mysql_password}';
|
||||
GRANT ALL PRIVILEGES ON \`${username}_%\`.* TO '${username}'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON \`${username}_%\`.* TO '${username}'@'%';
|
||||
GRANT CREATE USER ON *.* TO '${username}'@'localhost' WITH GRANT OPTION;
|
||||
|
@ -66,7 +66,7 @@ EOF
|
|||
|
||||
# Create site info file
|
||||
info_file="$main_web_root/site_info.txt"
|
||||
sudo bash -c "cat > $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 <<EOL
|
||||
$main_web_root/logs/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 14
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 0640 www-data adm
|
||||
sharedscripts
|
||||
dateext
|
||||
dateformat -%Y-%m-%d
|
||||
size 100M
|
||||
postrotate
|
||||
/usr/sbin/invoke-rc.d nginx rotate >/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"
|
||||
|
|
|
@ -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
|
|
@ -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+'@'<HOST>'
|
||||
ignoreregex =
|
||||
EOL"
|
||||
|
||||
# Create phpMyAdmin filter for fail2ban
|
||||
sudo bash -c "cat > /etc/fail2ban/filter.d/phpmyadmin.conf << EOL
|
||||
[Definition]
|
||||
failregex = ^<HOST> .* \"(GET|POST) /phpmyadmin/index\.php HTTP/.*\" 403
|
||||
^<HOST> .* \"(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"
|
57
install.sh
Normal file
57
install.sh
Normal file
|
@ -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"
|
61
install/00-update-install.sh
Normal file
61
install/00-update-install.sh
Normal file
|
@ -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."
|
54
install/01-nginx-default-root.sh
Normal file
54
install/01-nginx-default-root.sh
Normal file
|
@ -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
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Default Site</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Nothing here</h1>
|
||||
<p>There is nothing here</p>
|
||||
</body>
|
||||
</html>
|
||||
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."
|
18
install/certbot.sh
Normal file
18
install/certbot.sh
Normal file
|
@ -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
|
60
install/fail2ban-nginx.sh
Normal file
60
install/fail2ban-nginx.sh
Normal file
|
@ -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 = ^<HOST> .* "(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 = ^<HOST> .* "(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 = ^<HOST> .* "(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."
|
69
install/mysql.sh
Normal file
69
install/mysql.sh
Normal file
|
@ -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+'@'<HOST>'
|
||||
ignoreregex =
|
||||
EOL
|
||||
|
||||
# UFW setup
|
||||
ufw allow 3306/tcp
|
||||
|
||||
echo "MySQL installation, SSL configuration, and fail2ban setup completed."
|
57
install/sshd_websftpusers.sh
Normal file
57
install/sshd_websftpusers.sh
Normal file
|
@ -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."
|
26
install/swap.sh
Normal file
26
install/swap.sh
Normal file
|
@ -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
|
38
install/unattended-upgrades.sh
Normal file
38
install/unattended-upgrades.sh
Normal file
|
@ -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"
|
|
@ -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."
|
85
site-config.conf
Normal file
85
site-config.conf
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue