forked from aatishnn/lempstack
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlemp-debian.sh
executable file
·328 lines (280 loc) · 11 KB
/
lemp-debian.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
function pause(){
read -p "$*"
}
function check_root() {
if [ ! "$(whoami)" = "root" ]
then
echo "Root privilege required to run this script. Rerun as root."
exit 1
fi
}
check_root
function remove_incompatible_packages {
set +e # disable exit on error (in case not present)
apt-get remove apache2* certbot-auto
set -e # reenable exit on error
}
remove_incompatible_packages
function update {
apt-get update && apt-get upgrade -y
}
update
function set_available_php_version_from_repo {
# 1. List all packages exactly matching "php"
# 2. Remove useless polluting line ("Listing...")
# 3. Cut version numbers
# 4. Reverse sort version numbers (highest first)
# 5. Get first/highest result
# 6. Strip package epoch ("2" from "2:8.2+93")
# 7. Strip package subversion ("93" from "8.2+93")
PHP_VERSION=$(apt list -a "php" 2> /dev/null | grep -v "Listing..." | cut -d ' ' -f 2 | sort -Vr | head -n 1 | cut -d ':' -f 2 | cut -d '+' -f 1)
#PHP_VERSION="8.2"
}
set_available_php_version_from_repo
function install_packages {
apt-get install -y \
certbot \
curl \
lsb-release \
mariadb-server \
nginx \
openssl \
php${PHP_VERSION} \
php${PHP_VERSION}-apcu \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-fpm \
php${PHP_VERSION}-mysql \
python3-certbot-nginx \
wget
# alternative to install certbot:
#apt-get install snapd
#snap install core
#snap refresh core
#snap install --classic certbot
}
install_packages
function php_harden {
# php-snuffleupagus installation
# .deb releases on https://github.com/jvoisin/snuffleupagus/releases
# example: https://github.com/jvoisin/snuffleupagus/releases/download/v0.9.0/debian-bullseye-snuffleupagus_0.9.0_amd64.deb
snuffleupagus_deb_url=$(curl -s "https://api.github.com/repos/jvoisin/snuffleupagus/releases/latest" | grep "browser_download_url" | grep -oE "https://.+debian-$(lsb_release -sc)-snuffleupagus_.+_amd64.deb")
wget -O /tmp/snuffleupagus.deb "${snuffleupagus_deb_url}"
apt install -y /tmp/snuffleupagus.deb
rm /tmp/snuffleupagus.deb
#ln -s /etc/php/${PHP_VERSION}/mods-available/snuffleupagus.ini /etc/php/${PHP_VERSION}/fpm/conf.d/20-snuffleupagus.ini # doesn't always work, file is present in /8.1/ but linked from /8.2/ -> file not found
ini_path=$(find /etc/php -type f -name snuffleupagus.ini)
ln -s "${ini_path}" "/etc/php/${PHP_VERSION}/fpm/conf.d/20-snuffleupagus.ini" # link ini file
s_ver=$(echo ${ini_path} | cut -d '/' -f 4) # "8.1"
rules_path="/etc/php/${s_ver}/snuffleupagus.rules"
zcat "/usr/share/doc/snuffleupagus/examples/default.rules.gz" > "${rules_path}" # install rule file
sed -i "/;sp.configuration_file/d" "${ini_path}" # remove commented default line
echo "sp.configuration_file=${rules_path}" >> "${ini_path}" # add ${rules_path} to ini
set +e # disable exit on error (I have no idea why, but without it it doesn't work)
key=$(tr -dc 'A-F0-9' < /dev/urandom | head -c 128) # 128 char hex string
set -e # reenable exit on error
sed -i "s/# sp.global.secret_key.*/sp.global.secret_key(\"${key}\");/" "${rules_path}" # install key
}
php_harden
function stop_services {
service mariadb stop
service nginx stop
service php${PHP_VERSION}-fpm stop
}
stop_services
function write_config_files {
# defaults for web
cat > /etc/nginx/snippets/php_defaults <<END
index index.php index.html;
location / {
# This is cool because no php is touched for static content
try_files \$uri \$uri/ /index.php?q=\$uri&\$args;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)\$ {
expires 30m;
log_not_found off;
}
END
# for default and phpmyadmin
cat > /etc/nginx/snippets/php_no_vhost <<END
location ~ \.php\$ {
#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
try_files \$uri =404;
fastcgi_pass unix:/run/php/php${PHP_VERSION}-fpm.sock;
error_page 404 /404page.html;
}
END
# default config for headers
cat > /etc/nginx/snippets/headers_defaults <<END
#add_header Content-Security-Policy "default-src https:";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'";
add_header Referrer-Policy same-origin;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
#add_header X-Xss-Protection "1; mode=block" always;
add_header X-Xss-Protection "1; mode=block";
add_header Feature-Policy "accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; encrypted-media 'none'; fullscreen 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'none'; speaker 'none'; sync-xhr 'none'; usb 'none'; vr 'none'";
# TODO expand CSP
# TODO add Expect-CT
END
# default config for TLS
cat > /etc/nginx/snippets/tls_defaults <<END
## https://ssl-config.mozilla.org/#server=nginx&version=1.18&config=modern&openssl=1.1.1n&guideline=5.6
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# modern configuration
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
#add_header Strict-Transport-Security "max-age=63072000" always;
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload";
# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;
resolver 9.9.9.9 8.8.8.8 valid=300s;
resolver_timeout 5s;
## my own additions
ssl_ecdh_curve secp384r1;
# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/ssl/certs/dhparam.pem;
END
# nginx config
# server tokens
sed -i "s/# server_tokens.*;/server_tokens off;/g" /etc/nginx/nginx.conf
sed -i "s/server_tokens.*;/server_tokens off;/g" /etc/nginx/nginx.conf
# gzip_types based on http://arstechnica.com/gadgets/2012/11/how-to-set-up-a-safe-and-secure-web-server/3/
sed -i "s/# gzip_types.*/gzip_types text\/plain text\/css application\/json application\/javascript text\/xml application\/xml application\/xml+rss image\/svg+xml application\/x-font-ttf font\/opentype application\/vnd.ms-fontobject;/" /etc/nginx/nginx.conf
# enable gzip
sed -i "s/# gzip_/gzip_/g" /etc/nginx/nginx.conf
# nginx default page, set to php instead of default html
cat > /etc/nginx/sites-available/default <<END
server {
server_name _;
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/;
include snippets/php_defaults;
include snippets/php_no_vhost;
}
END
# install php-fpm config
# /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
# /run/php/php${PHP_VERSION}-fpm.sock
sed -i "s/;pm.max_requests/pm.max_requests/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
sed -i "s/;chdir/chdir/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
sed -i "s/;catch_workers_output/catch_workers_output/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
sed -i "s/;env\[HOSTNAME\]/env\[HOSTNAME\]/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
sed -i "s/;env\[TMP\]/env\[TMP\]/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
sed -i "s/;env\[TMPDIR\]/env\[TMPDIR\]/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
sed -i "s/;env\[TEMP\]/env\[TEMP\]/g" /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
echo "php_admin_value[upload_max_filesize] = 128M" >> /etc/php/${PHP_VERSION}/fpm/pool.d/www.conf
# php.ini setup
sed -i 's/cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' /etc/php/${PHP_VERSION}/fpm/php.ini
sed -i 's/;cgi.fix_pathinfo=0/cgi.fix_pathinfo=0/g' /etc/php/${PHP_VERSION}/fpm/php.ini
sed -i 's/opcache.enable=0/opcache.enable=1/g' /etc/php/${PHP_VERSION}/fpm/php.ini
sed -i 's/;opcache.enable=1/opcache.enable=1/g' /etc/php/${PHP_VERSION}/fpm/php.ini
}
write_config_files
function install_pma {
echo -n "Install PHPMyAdmin? [y/N]:"
read pma_install
if [[ "${pma_install}" == "y" || "${pma_install}" == "Y" ]]
then
echo "Installing PhpMyAdmin"
echo "Don't select any options and select no to configure with dbcommon."
pause "Press [Enter] key to continue after reading the above line ..."
apt-get install -y phpmyadmin
echo -n "Domain for PHPMyAdmin Web Interface? Example:pma.domain.com :"
read pma_url
cat > "/etc/nginx/sites-available/$pma_url.conf" <<END
server {
server_name $pma_url;
root /usr/share/phpmyadmin;
include snippets/php_defaults;
include snippets/php_no_vhost;
access_log /var/log/nginx/$pma_url-access.log;
error_log /var/log/nginx/$pma_url-error.log;
}
END
ln -s "/etc/nginx/sites-available/$pma_url.conf" "/etc/nginx/sites-enabled/$pma_url.conf"
else
echo "Skipping PhpMyAdmin Installation"
fi
}
function init_mariadb {
service mariadb restart
echo "Running mysql_secure_installation. Use root password if set during install time."
pause "Press [Enter] key to continue after reading the above line ..."
mysql_secure_installation
echo "mysql_secure_installation done."
echo "Warning: The next step can take several hours! You can do this later by executing 'openssl dhparam -out /etc/ssl/certs/dhparam.pem [numbits]'."
echo -n "Do you want to create /etc/ssl/certs/dhparam.pem now? [Y/n]"
read -r dhparam
if [[ -z "${dhparam}" || "${dhparam}" == "y" || "${dhparam}" == "Y" ]]
then
echo -n "How many bits long should the dhparam file be? [4096]"
read -r numbits
regex='^[0-9]+$'
if ! [[ "$numbits" =~ $regex ]]
then
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
else
openssl dhparam -out /etc/ssl/certs/dhparam.pem "$numbits"
fi
fi
}
function ask_questions {
# remove /var/www/html
echo "Answer yes to remove the default /var/www/html folder"
rm -r -I /var/www/html
# set ownership of /var/www
chown -R www-data:www-data /var/www
install_pma
init_mariadb
}
ask_questions
function restart_services {
service php${PHP_VERSION}-fpm start # use start here instead of restart
service nginx restart
service mariadb restart
}
restart_services
function set_sysctl {
# https://dev.chromium.org/spdy/spdy-best-practices
# Turn off tcp_slow_start_after_idle
set +e # disable exit on error (containers usually don't allow to write sysctl values)
sysctl -w net.ipv4.tcp_slow_start_after_idle=0
set -e # reenable exit on error
echo "net.ipv4.tcp_slow_start_after_idle = 0" > /etc/sysctl.d/10-net.ipv4.tcp_slow_start_after_idle.conf
}
set_sysctl
function download_other_scripts {
# TODO
wget https://github.com/mdPlusPlus/lempstack/raw/master/setup-vhost.sh -O /bin/setup-vhost
wget https://github.com/mdPlusPlus/lempstack/raw/master/setup-proxy-only.sh -o /bin/setup-proxy-only
chmod 755 /bin/setup-vhost /bin/setup-proxy-only
}
#download_other_scripts
function finished {
echo "Installation done."
echo "Use setup-vhost to configure virtual hosts."
exit
}
finished