-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdrupal-lab.sh
More file actions
executable file
·357 lines (291 loc) · 9.17 KB
/
drupal-lab.sh
File metadata and controls
executable file
·357 lines (291 loc) · 9.17 KB
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#!/bin/bash
DDEV="ddev"
check_ddev() {
if ! command -v "$DDEV" &> /dev/null; then
printf "DDEV is not installed. Please install DDEV before running this script.\n" >&2
return 1
fi
return 0
}
new_project() {
local machine_name site_name
read -r -p "Enter the machine name: " machine_name
if [[ ! "$machine_name" =~ ^[a-z][-a-z0-9]*$ ]]; then
printf "Invalid machine name. It must start with a lowercase letter, and can only contain lowercase letters, digits, and hyphens.\n" >&2
return 1
fi
read -r -p "Enter the site name: " site_name
if [[ -z "$site_name" ]]; then
printf "Site name cannot be empty.\n" >&2
return 1
fi
\$ma
if [[ -d "$machine_name" ]]; then
printf "Directory '%s' already exists. Please choose a different machine name.\n" "$machine_name" >&2
return 1
fi
mkdir -p "$machine_name"
cd "$machine_name" || return 1
"$DDEV" config --project-type=drupal --php-version=8.3 --docroot=web --project-name="$machine_name"
"$DDEV" start || return 1
"$DDEV" composer create drupal/recommended-project:^10 || return 1
mkdir -p config/sync
# Create settings.prod.php in web/sites/default/
cat <<'EOL' > ./web/sites/default/settings.prod.php
<?php
/**
* @file
* Production-specific configuration file.
*
*/
// ** Environment Variables Configuration **
// Database settings
$databases['default']['default'] = [
'driver' => 'mysql',
'database' => getenv('DB_NAME'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASSWORD'),
'host' => getenv('DB_HOST'),
'port' => getenv('DB_PORT'),
'prefix' => '',
'collation' => 'utf8mb4_general_ci',
'charset' => 'utf8mb4',
];
// Trusted host patterns to prevent host header poisoning
$settings['trusted_host_patterns'] = [
'^' . preg_quote(getenv('DRUPAL_TRUSTED_HOST_PATTERN') ?: 'www.example.com') . '$',
];
// Hash salt for security
$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT') ?: 'random-hash-value';
// File system paths
$settings['file_public_path'] = getenv('DRUPAL_FILE_PUBLIC_PATH') ?: 'sites/default/files';
$settings['file_private_path'] = getenv('DRUPAL_FILE_PRIVATE_PATH') ?: 'sites/default/files/private';
$settings['file_temp_path'] = getenv('DRUPAL_FILE_TEMP_PATH') ?: '/tmp';
// Disable development services
//$config['system.logging']['error_level'] = 'hide';
//$config['system.performance']['cache']['page']['max_age'] = 900;
//$config['system.performance']['css']['preprocess'] = TRUE;
//$config['system.performance']['js']['preprocess'] = TRUE;
//$settings['cache']['default'] = 'cache.backend.redis';
//$settings['redis.connection']['interface'] = 'PhpRedis';
//$settings['redis.connection']['host'] = getenv('REDIS_HOST') ?: '127.0.0.1';
//$settings['redis.connection']['port'] = getenv('REDIS_PORT') ?: 6379;
// Set session cookie to be secure
ini_set('session.cookie_secure', '1');
// Reverse proxy settings (if behind a load balancer or reverse proxy)
if (getenv('DRUPAL_REVERSE_PROXY') === 'true') {
$settings['reverse_proxy'] = TRUE;
$settings['reverse_proxy_addresses'] = explode(',', getenv('DRUPAL_REVERSE_PROXY_ADDRESSES') ?: '');
}
// Other recommended settings for production
$settings['update_free_access'] = FALSE;
$settings['allow_authorize_operations'] = FALSE;
$settings['skip_permissions_hardening'] = TRUE;
EOL
# Append to web/sites/default/settings.php
cat <<'EOL' >> ./web/sites/default/settings.php
$settings['config_sync_directory'] = '../config/sync';
if (getenv('DRUPAL_ENVIRONMENT')) {
$env = getenv('DRUPAL_ENVIRONMENT');
$settings_file = __DIR__ . "/settings.$env.php";
if (file_exists($settings_file)) {
include $settings_file;
}
} else {
if (file_exists(__DIR__ . '/settings.local.php')) {
include __DIR__ . '/settings.local.php';
}
}
EOL
# Create .build/Dockerfile and Caddyfile
mkdir -p .build
cat <<'EOL' > ./.build/Dockerfile
# Stage 1: Build stage
FROM php:8.3-cli AS build
# Install build dependencies and PHP extensions in one RUN command
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libjpeg-dev \
libwebp-dev \
libfreetype6-dev \
libzip-dev \
libicu-dev \
libxml2-dev \
libonig-dev \
unzip \
bash \
&& rm -rf /var/lib/apt/lists/* \
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install -j$(nproc) gd intl zip opcache pdo pdo_mysql mbstring bcmath
# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/html
# Copy Composer files and install dependencies
COPY ./composer.json ./composer.lock ./
ENV COMPOSER_ALLOW_SUPERUSER 1
RUN composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist
# Copy the entire project to the container
COPY . /var/www/html
# Set permissions and create necessary directories
RUN chown -R www-data:www-data /var/www/html \
&& mkdir -p sites/default/files \
&& find sites/default/files -type d -exec chmod 755 {} \; \
&& find sites/default/files -type f -exec chmod 644 {} \;
# Stage 2: Runtime stage with FrankenPHP and Caddy
FROM dunglas/frankenphp:1-php8.3
RUN install-php-extensions \
apcu \
gd \
opcache \
pdo_mysql \
zip
COPY --from=drupal:php8.3 /usr/local/etc/php/conf.d/* /usr/local/etc/php/conf.d/
COPY --from=build /var/www/html /opt/drupal
COPY .build/Caddyfile /etc/caddy/Caddyfile
WORKDIR /opt/drupal
ENV PATH=${PATH}:/opt/drupal/vendor/bin
EOL
# Create .build/Caddyfile
cat <<'EOL' > ./.build/Caddyfile
{
{$CADDY_GLOBAL_OPTIONS}
frankenphp {
{$FRANKENPHP_CONFIG}
}
# https://caddyserver.com/docs/caddyfile/directives#sorting-algorithm
order php_server before file_server
order php before file_server
}
{$CADDY_EXTRA_CONFIG}
:80 {
root * web/
encode zstd br gzip
@hiddenPhpFilesRegexp path_regexp \..*/.*\.php$
error @hiddenPhpFilesRegexp 403
@notFoundPhpFiles path_regexp /vendor/.*\.php$
error @notFoundPhpFiles 404
@notFoundPhpFilesRegexp path_regexp ^/sites/[^/]+/files/.*\.php$
error @notFoundPhpFilesRegexp 404
@privateDirRegexp path_regexp ^/sites/.*/private/
error @privateDirRegexp 403
@protectedFilesRegexp {
not path /.well-known*
path_regexp \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^/(\..*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config|yarn\.lock|package\.json)$|^\/#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$
}
error @protectedFilesRegexp 403
@static {
file
path *.avif *.css *.eot *.gif *.gz *.ico *.jpg *.jpeg *.js *.otf *.pdf *.png *.svg *.ttf *.webp *.woff *.woff2
}
header @static Cache-Control "max-age=31536000,public,immutable"
{$CADDY_SERVER_EXTRA_DIRECTIVES}
php_server
}
EOL
# Create .dockerignore
cat <<'EOL' > ./.dockerignore
.ddev/
vendor/
.git/
.github/
node_modules/
tests/
build/
web/sites/default/files/
.env
README.md
.gitattributes
EOL
# Create .gitignore
cat <<'EOL' > ./.gitignore
# Ignore directories generated by Composer
/vendor/
/web/core/
/web/modules/contrib/
/web/themes/contrib/
/web/profiles/contrib/
/web/libraries/
# Ignore files generated by PhpStorm
/.idea/
/.editorconfig
/.gitattributes
/grumphp.yml
/behat.yml
/phpunit.xml
/phpunit.result.cache
/phpcs.xml
console/
# Ignore mounts
web/sites/default/files
tmp
private
.drush
drush-backups
.console
EOL
"$DDEV" config --update || return 1
"$DDEV" composer require drush/drush || return 1
"$DDEV" drush site:install --account-name=admin --account-pass=admin --site-name="$site_name" -y || return 1
"$DDEV" composer require drupal/gin_toolbar:^1.0@rc drupal/gin:^3.0@rc || return 1
"$DDEV" drush theme:enable gin -y || return 1
"$DDEV" drush config-set system.theme admin gin -y || return 1
"$DDEV" drush cex -y || return 1
printf "New Drupal project '%s' created with Gin admin theme and site name '%s'.\n" "$machine_name" "$site_name"
}
remove_project() {
local project_name=$1
if [[ -z "$project_name" ]]; then
printf "Please provide a project name.\n" >&2
return 1
fi
"$DDEV" stop -a || return 1
"$DDEV" rm -a || return 1
rm -rf "$project_name" || return 1
printf "Project %s removed.\n" "$project_name"
}
remove_all() {
"$DDEV" stop -a || return 1
"$DDEV" rm -a || return 1
printf "All DDEV projects removed.\n"
}
build_project() {
local machine_name=$1
if [[ -z "$machine_name" ]]; then
printf "Please provide an image name (you can use : for tagging).\n" >&2
return 1
fi
# Copy Dockerfile and Caddyfile to .build
cp ./build-files/Dockerfile .build/Dockerfile
cp ./build-files/Caddyfile .build/Caddyfile
docker build -f .build/Dockerfile -t "${machine_name}" . || return 1
printf "Docker image '%s' built successfully.\n" "${machine_name}"
}
main() {
if [[ "$#" -lt 1 ]]; then
printf "Usage: drupal-lab {new|remove|remove-all|build} [project-name]\n" >&2
return 1
fi
check_ddev || return 1
case "$1" in
new)
new_project "$2"
;;
remove)
remove_project "$2"
;;
remove-all)
remove_all
;;
build)
build_project "$2"
;;
*)
printf "Invalid command. Usage: drupal-lab {new|remove|remove-all|build} [project-name]\n" >&2
return 1
;;
esac
}
main "$@"