nginx-apache-site 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Setup Node
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: "20"
20
+ cache: npm
21
+
22
+ - name: Install dependencies
23
+ run: npm ci
24
+
25
+ - name: Shell syntax check
26
+ run: bash -n script/create-web-site.sh script/create-apache-site.sh script/create-nginx-site.sh tests/test-cli.sh tests/linux-smoke.sh
27
+
28
+ - name: Unit tests
29
+ run: npm test
30
+
31
+ - name: Linux smoke tests
32
+ run: npm run test:linux
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # nginx-apache-site
2
+ `nginx-apache-site` is a merged replacement for `create-apache-site` and
3
+ `create-nginx-site`.
4
+
5
+ It creates:
6
+ - Website directory and starter static files in `/var/www/<domain>/public_html`
7
+ - Apache or Nginx virtual host config
8
+ - Optional Certbot SSL certificate
9
+
10
+ The package also ships compatibility wrappers:
11
+ - `create-apache-site`
12
+ - `create-nginx-site`
13
+
14
+ ## Install
15
+ ```bash
16
+ npm install -g nginx-apache-site
17
+ ```
18
+
19
+ ## Usage
20
+ Unified command:
21
+ ```bash
22
+ nginx-apache-site --domain example.com --server nginx
23
+ nginx-apache-site --domain example.com --server apache
24
+ nginx-apache-site --domain example.com --server auto
25
+ nginx-apache-site --domain example.com
26
+ ```
27
+
28
+ If `--server` is omitted, the CLI prompts on interactive terminals with default
29
+ `auto`. In non-interactive runs, it defaults to `auto` directly.
30
+
31
+ Compatibility wrappers:
32
+ ```bash
33
+ create-apache-site --domain example.com
34
+ create-nginx-site --domain example.com
35
+ ```
36
+
37
+ ### SSL options
38
+ - `--ssl` always run Certbot
39
+ - `--no-ssl` skip Certbot
40
+ - no SSL flag: interactive prompt when running in a terminal
41
+
42
+ Extra Certbot controls:
43
+ - `--certbot-email admin@example.com`
44
+ - `--certbot-staging`
45
+ - `--yes` auto-confirm SSL prompt
46
+
47
+ ## Common examples
48
+ ```bash
49
+ # Nginx with SSL and Certbot email
50
+ nginx-apache-site -d example.com -s nginx --ssl --certbot-email admin@example.com
51
+
52
+ # Apache without SSL
53
+ nginx-apache-site -d example.com -s apache --no-ssl
54
+
55
+ # Preview actions only
56
+ nginx-apache-site -d example.com -s nginx --ssl --dry-run
57
+ ```
58
+
59
+ ## Requirements
60
+ - Ubuntu/Debian-style Apache or Nginx layout
61
+ - Root privileges (`sudo`) for non-dry-run mode
62
+ - Certbot plugin for your server type if using SSL
63
+
64
+ ## Development tests
65
+ ```bash
66
+ npm test
67
+ npm run test:linux
68
+ ```
69
+
70
+ `npm test` runs local shell tests with mocked system commands, so it does not
71
+ modify your host web server setup.
72
+
73
+ `npm run test:linux` adds Linux-specific smoke checks. CI runs both test suites
74
+ on `ubuntu-latest` for every push and pull request.
@@ -0,0 +1,14 @@
1
+ <VirtualHost *:80>
2
+ ServerName {{DOMAIN_NAME}}
3
+ ServerAlias www.{{DOMAIN_NAME}}
4
+
5
+ DocumentRoot {{WEB_ROOT}}/{{DOMAIN_NAME}}/public_html
6
+ <Directory {{WEB_ROOT}}/{{DOMAIN_NAME}}/public_html>
7
+ Options Indexes FollowSymLinks
8
+ AllowOverride All
9
+ Require all granted
10
+ </Directory>
11
+
12
+ ErrorLog ${APACHE_LOG_DIR}/{{DOMAIN_NAME}}-error.log
13
+ CustomLog ${APACHE_LOG_DIR}/{{DOMAIN_NAME}}-access.log combined
14
+ </VirtualHost>
@@ -0,0 +1,12 @@
1
+ server {
2
+ listen 80;
3
+ listen [::]:80;
4
+ server_name {{DOMAIN_NAME}} www.{{DOMAIN_NAME}};
5
+
6
+ root {{WEB_ROOT}}/{{DOMAIN_NAME}}/public_html;
7
+ index index.html index.htm;
8
+
9
+ location / {
10
+ try_files $uri $uri/ =404;
11
+ }
12
+ }
@@ -0,0 +1,45 @@
1
+ /* reset */
2
+ * {
3
+ font-family: sans-serif;
4
+ }
5
+ *::selection {
6
+ background: #f00;
7
+ color: #fff;
8
+ }
9
+
10
+ /* background-color: #3600d9; */
11
+
12
+ body,
13
+ h1,
14
+ p {
15
+ margin: 0;
16
+ padding: 0;
17
+ }
18
+
19
+ /* Center align the content */
20
+ body {
21
+ display: flex;
22
+ justify-content: center;
23
+ align-items: center;
24
+ height: 100vh;
25
+ background-color: #f5f5f5;
26
+ font-family: Arial, sans-serif;
27
+ }
28
+
29
+ /* Style for the title */
30
+ h1 {
31
+ font-size: 36px;
32
+ font-weight: bold;
33
+ text-align: center;
34
+ margin-bottom: 20px;
35
+ }
36
+
37
+ /* Style for the subtitle */
38
+ p {
39
+ font-size: 18px;
40
+ text-align: center;
41
+ }
42
+
43
+ strong {
44
+ color: #ff0f33;
45
+ }
Binary file
@@ -0,0 +1 @@
1
+ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+
8
+ <!-- title -->
9
+ <title>HI | {{DOMAIN_NAME}}</title>
10
+
11
+ <!-- CSS -->
12
+ <link rel="stylesheet" href="./css/styles.css">
13
+
14
+ <!-- base SEO -->
15
+ <meta name="description" content="">
16
+ <meta name="keywords" content="">
17
+
18
+ <!-- favicon -->
19
+ <link rel="apple-touch-icon" sizes="180x180" href="./img/apple-touch-icon.png">
20
+ <link rel="icon" type="image/png" sizes="32x32" href="./img/favicon-32x32.png">
21
+ <link rel="icon" type="image/png" sizes="16x16" href="./img/favicon-16x16.png">
22
+ <link rel="manifest" href="./img/site.webmanifest">
23
+
24
+ </head>
25
+
26
+ <body>
27
+ <div id="container">
28
+ <h1>Welcome to <strong>{{DOMAIN_NAME}}</strong></h1>
29
+ <p>Thank you for visiting our website. Please check back later for more information.</p>
30
+ </div>
31
+ </body>
32
+
33
+ </html>
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "nginx-apache-site",
3
+ "version": "1.0.0",
4
+ "description": "Create Apache or Nginx website configs and optional Certbot SSL certificates on Ubuntu.",
5
+ "bin": {
6
+ "nginx-apache-site": "script/create-web-site.sh",
7
+ "create-apache-site": "script/create-apache-site.sh",
8
+ "create-nginx-site": "script/create-nginx-site.sh"
9
+ },
10
+ "scripts": {
11
+ "test": "bash tests/test-cli.sh",
12
+ "test:linux": "bash tests/linux-smoke.sh"
13
+ },
14
+ "keywords": [
15
+ "apache",
16
+ "nginx",
17
+ "certbot",
18
+ "ssl",
19
+ "ubuntu",
20
+ "website",
21
+ "virtual-host",
22
+ "server-config"
23
+ ],
24
+ "author": "ai-armageddon",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/ai-armageddon/create-web-site.git"
29
+ },
30
+ "homepage": "https://github.com/ai-armageddon/create-web-site"
31
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ exec "${script_dir}/create-web-site.sh" --server apache "$@"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ exec "${script_dir}/create-web-site.sh" --server nginx "$@"
@@ -0,0 +1,439 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ readonly PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
6
+ readonly STATIC_DIR="${PROJECT_DIR}/assets/static"
7
+ readonly SERVER_TEMPLATE_DIR="${PROJECT_DIR}/assets/server"
8
+
9
+ WEB_ROOT="${WEB_SITE_ROOT:-/var/www}"
10
+ APACHE_AVAILABLE_DIR="${WEB_SITE_APACHE_AVAILABLE_DIR:-/etc/apache2/sites-available}"
11
+ NGINX_AVAILABLE_DIR="${WEB_SITE_NGINX_AVAILABLE_DIR:-/etc/nginx/sites-available}"
12
+ NGINX_ENABLED_DIR="${WEB_SITE_NGINX_ENABLED_DIR:-/etc/nginx/sites-enabled}"
13
+ SYSTEMCTL_BIN="${WEB_SITE_SYSTEMCTL_BIN:-systemctl}"
14
+ A2ENSITE_BIN="${WEB_SITE_A2ENSITE_BIN:-a2ensite}"
15
+ CERTBOT_BIN="${WEB_SITE_CERTBOT_BIN:-certbot}"
16
+
17
+ fail() {
18
+ printf 'Error: %s\n' "$1" >&2
19
+ exit 1
20
+ }
21
+
22
+ print_usage() {
23
+ cat <<'EOF'
24
+ Usage:
25
+ nginx-apache-site -d|--domain <domain> [-s|--server <apache|nginx|auto>] [options]
26
+
27
+ Server shortcuts:
28
+ create-apache-site -d|--domain <domain> [options]
29
+ create-nginx-site -d|--domain <domain> [options]
30
+
31
+ Options:
32
+ -d, --domain <domain> Domain name to configure.
33
+ -s, --server <type> Web server: apache, nginx, or auto.
34
+ --auto Shortcut for --server auto.
35
+ --apache Shortcut for --server apache.
36
+ --nginx Shortcut for --server nginx.
37
+ --ssl Always request a Certbot certificate.
38
+ --no-ssl Skip Certbot certificate request.
39
+ --certbot-email <email> Email address for Certbot.
40
+ --certbot-staging Use Certbot staging endpoint.
41
+ --yes Auto-confirm SSL prompt when applicable.
42
+ --dry-run Print planned actions without making changes.
43
+ -h, --help Show this message.
44
+ EOF
45
+ }
46
+
47
+ is_command_available() {
48
+ command -v "$1" >/dev/null 2>&1
49
+ }
50
+
51
+ escape_for_sed_replacement() {
52
+ printf '%s' "$1" | sed -e 's/[\/&#\\]/\\&/g'
53
+ }
54
+
55
+ run_cmd() {
56
+ if [[ "${dry_run}" == "true" ]]; then
57
+ printf '[dry-run] %q' "$1"
58
+ shift
59
+ for arg in "$@"; do
60
+ printf ' %q' "${arg}"
61
+ done
62
+ printf '\n'
63
+ return 0
64
+ fi
65
+
66
+ "$@"
67
+ }
68
+
69
+ replace_placeholders_in_file() {
70
+ local file_path="$1"
71
+ local escaped_domain
72
+ local escaped_web_root
73
+
74
+ escaped_domain="$(escape_for_sed_replacement "${domain_name}")"
75
+ escaped_web_root="$(escape_for_sed_replacement "${WEB_ROOT}")"
76
+
77
+ if [[ "${dry_run}" == "true" ]]; then
78
+ printf '[dry-run] render template %s\n' "${file_path}"
79
+ return 0
80
+ fi
81
+
82
+ sed -i.bak \
83
+ -e "s/{{DOMAIN_NAME}}/${escaped_domain}/g" \
84
+ -e "s/{{WEB_ROOT}}/${escaped_web_root}/g" \
85
+ "${file_path}"
86
+ rm -f "${file_path}.bak"
87
+ }
88
+
89
+ validate_domain() {
90
+ [[ "${domain_name}" =~ ^[A-Za-z0-9][A-Za-z0-9.-]*[A-Za-z0-9]$ ]] \
91
+ || fail "Invalid domain '${domain_name}'."
92
+
93
+ [[ "${domain_name}" == *.* ]] \
94
+ || fail "Domain '${domain_name}' must include at least one dot."
95
+ }
96
+
97
+ validate_requested_server_type() {
98
+ case "${server_type}" in
99
+ ""|auto|apache|nginx) ;;
100
+ *)
101
+ fail "Unsupported server '${server_type}'. Use apache, nginx, or auto."
102
+ ;;
103
+ esac
104
+ }
105
+
106
+ validate_server_type() {
107
+ case "${server_type}" in
108
+ apache|nginx) ;;
109
+ *)
110
+ fail "Unsupported server '${server_type}'. Use apache or nginx."
111
+ ;;
112
+ esac
113
+ }
114
+
115
+ prompt_for_server_type_if_needed() {
116
+ local selection
117
+
118
+ if [[ -n "${server_type}" ]]; then
119
+ return
120
+ fi
121
+
122
+ if [[ -t 0 ]]; then
123
+ read -r -p "Web server [auto/apache/nginx] (default: auto): " selection || true
124
+ selection="${selection,,}"
125
+ server_type="${selection:-auto}"
126
+ else
127
+ server_type="auto"
128
+ fi
129
+
130
+ validate_requested_server_type
131
+ }
132
+
133
+ resolve_auto_server_type() {
134
+ local apache_score=0
135
+ local nginx_score=0
136
+
137
+ if [[ "${server_type}" != "auto" ]]; then
138
+ return
139
+ fi
140
+
141
+ if [[ -d "${APACHE_AVAILABLE_DIR}" ]]; then
142
+ apache_score=$((apache_score + 2))
143
+ fi
144
+ if is_command_available "${A2ENSITE_BIN}"; then
145
+ apache_score=$((apache_score + 2))
146
+ fi
147
+ if [[ -d "/etc/apache2" ]]; then
148
+ apache_score=$((apache_score + 1))
149
+ fi
150
+
151
+ if [[ -d "${NGINX_AVAILABLE_DIR}" ]]; then
152
+ nginx_score=$((nginx_score + 2))
153
+ fi
154
+ if [[ -d "${NGINX_ENABLED_DIR}" ]]; then
155
+ nginx_score=$((nginx_score + 1))
156
+ fi
157
+ if is_command_available nginx; then
158
+ nginx_score=$((nginx_score + 1))
159
+ fi
160
+ if [[ -d "/etc/nginx" ]]; then
161
+ nginx_score=$((nginx_score + 1))
162
+ fi
163
+
164
+ if [[ "${nginx_score}" -eq 0 ]] && [[ "${apache_score}" -eq 0 ]]; then
165
+ fail "Auto-detection could not find Apache or Nginx. Use --server apache or --server nginx."
166
+ fi
167
+
168
+ if [[ "${nginx_score}" -ge "${apache_score}" ]]; then
169
+ server_type="nginx"
170
+ else
171
+ server_type="apache"
172
+ fi
173
+
174
+ printf 'Auto-selected server: %s\n' "${server_type}"
175
+ }
176
+
177
+ warn_if_not_root() {
178
+ if [[ "${dry_run}" == "false" ]] && [[ "${EUID}" -ne 0 ]]; then
179
+ printf 'Warning: running without root; writes may fail for system paths.\n' >&2
180
+ fi
181
+ }
182
+
183
+ ask_yes_no() {
184
+ local question="$1"
185
+ local response
186
+ read -r -p "${question} [y/N]: " response || true
187
+ [[ "${response}" =~ ^[Yy]([Ee][Ss])?$ ]]
188
+ }
189
+
190
+ decide_ssl_mode() {
191
+ case "${ssl_mode}" in
192
+ yes)
193
+ should_run_ssl="true"
194
+ ;;
195
+ no)
196
+ should_run_ssl="false"
197
+ ;;
198
+ ask)
199
+ if [[ "${assume_yes}" == "true" ]]; then
200
+ should_run_ssl="true"
201
+ elif [[ -t 0 ]]; then
202
+ if ask_yes_no "Request SSL certificate from Certbot now?"; then
203
+ should_run_ssl="true"
204
+ else
205
+ should_run_ssl="false"
206
+ fi
207
+ else
208
+ should_run_ssl="false"
209
+ fi
210
+ ;;
211
+ *)
212
+ fail "Unexpected SSL mode '${ssl_mode}'."
213
+ ;;
214
+ esac
215
+ }
216
+
217
+ resolve_certbot_identity() {
218
+ if [[ "${should_run_ssl}" != "true" ]]; then
219
+ return
220
+ fi
221
+
222
+ if [[ -z "${certbot_email}" ]] && [[ -t 0 ]] && [[ "${assume_yes}" != "true" ]]; then
223
+ read -r -p "Certbot email (leave blank to skip): " certbot_email || true
224
+ fi
225
+ }
226
+
227
+ check_required_commands() {
228
+ local needed=("cp" "mkdir" "sed")
229
+ if [[ "${server_type}" == "apache" ]]; then
230
+ needed+=("${A2ENSITE_BIN}" "${SYSTEMCTL_BIN}")
231
+ else
232
+ needed+=("ln" "${SYSTEMCTL_BIN}")
233
+ fi
234
+
235
+ if [[ "${should_run_ssl}" == "true" ]]; then
236
+ needed+=("${CERTBOT_BIN}")
237
+ fi
238
+
239
+ local cmd
240
+ for cmd in "${needed[@]}"; do
241
+ if ! is_command_available "${cmd}"; then
242
+ fail "Missing required command: ${cmd}"
243
+ fi
244
+ done
245
+ }
246
+
247
+ create_site_directory() {
248
+ local site_path="${WEB_ROOT}/${domain_name}/public_html"
249
+ run_cmd mkdir -p "${site_path}"
250
+ run_cmd cp -R "${STATIC_DIR}/." "${site_path}/"
251
+ replace_placeholders_in_file "${site_path}/index.html"
252
+ printf 'Created site files in %s\n' "${site_path}"
253
+ }
254
+
255
+ create_server_config() {
256
+ local template_path
257
+ local config_path
258
+
259
+ if [[ "${server_type}" == "apache" ]]; then
260
+ template_path="${SERVER_TEMPLATE_DIR}/apache.template.conf"
261
+ config_path="${APACHE_AVAILABLE_DIR}/${domain_name}.conf"
262
+ run_cmd mkdir -p "${APACHE_AVAILABLE_DIR}"
263
+ else
264
+ template_path="${SERVER_TEMPLATE_DIR}/nginx.template.conf"
265
+ config_path="${NGINX_AVAILABLE_DIR}/${domain_name}.conf"
266
+ run_cmd mkdir -p "${NGINX_AVAILABLE_DIR}"
267
+ fi
268
+
269
+ [[ -f "${template_path}" ]] || fail "Missing template file: ${template_path}"
270
+
271
+ run_cmd cp "${template_path}" "${config_path}"
272
+ replace_placeholders_in_file "${config_path}"
273
+ printf 'Created %s config: %s\n' "${server_type}" "${config_path}"
274
+ }
275
+
276
+ enable_apache_site() {
277
+ run_cmd "${A2ENSITE_BIN}" "${domain_name}.conf"
278
+ run_cmd "${SYSTEMCTL_BIN}" reload apache2
279
+ run_cmd "${SYSTEMCTL_BIN}" restart apache2
280
+ printf 'Enabled Apache site %s\n' "${domain_name}"
281
+ }
282
+
283
+ enable_nginx_site() {
284
+ local config_path="${NGINX_AVAILABLE_DIR}/${domain_name}.conf"
285
+ local symlink_path="${NGINX_ENABLED_DIR}/${domain_name}.conf"
286
+
287
+ run_cmd mkdir -p "${NGINX_ENABLED_DIR}"
288
+
289
+ if [[ -e "${symlink_path}" ]] || [[ -L "${symlink_path}" ]]; then
290
+ printf 'Nginx symlink already exists: %s\n' "${symlink_path}"
291
+ else
292
+ run_cmd ln -s "${config_path}" "${symlink_path}"
293
+ printf 'Created Nginx symlink: %s\n' "${symlink_path}"
294
+ fi
295
+
296
+ run_cmd "${SYSTEMCTL_BIN}" reload nginx
297
+ run_cmd "${SYSTEMCTL_BIN}" restart nginx
298
+ printf 'Enabled Nginx site %s\n' "${domain_name}"
299
+ }
300
+
301
+ run_certbot() {
302
+ local certbot_args=(
303
+ "--${server_type}"
304
+ "-d" "${domain_name}"
305
+ "-d" "www.${domain_name}"
306
+ "--redirect"
307
+ "--non-interactive"
308
+ "--agree-tos"
309
+ )
310
+
311
+ if [[ -n "${certbot_email}" ]]; then
312
+ certbot_args+=("--email" "${certbot_email}")
313
+ else
314
+ certbot_args+=("--register-unsafely-without-email")
315
+ fi
316
+
317
+ if [[ "${certbot_staging}" == "true" ]]; then
318
+ certbot_args+=("--staging")
319
+ fi
320
+
321
+ run_cmd "${CERTBOT_BIN}" "${certbot_args[@]}"
322
+ printf 'Requested SSL certificate via Certbot for %s\n' "${domain_name}"
323
+ }
324
+
325
+ domain_name=""
326
+ server_type=""
327
+ ssl_mode="ask"
328
+ should_run_ssl="false"
329
+ certbot_email=""
330
+ certbot_staging="false"
331
+ assume_yes="false"
332
+ dry_run="false"
333
+
334
+ case "$(basename "$0")" in
335
+ create-apache-site|create-apache-site.sh)
336
+ server_type="apache"
337
+ ;;
338
+ create-nginx-site|create-nginx-site.sh)
339
+ server_type="nginx"
340
+ ;;
341
+ esac
342
+
343
+ while [[ $# -gt 0 ]]; do
344
+ case "$1" in
345
+ -d|--domain)
346
+ [[ $# -ge 2 ]] || fail "Missing value for $1"
347
+ domain_name="$2"
348
+ shift 2
349
+ ;;
350
+ -s|--server)
351
+ [[ $# -ge 2 ]] || fail "Missing value for $1"
352
+ server_type="$2"
353
+ shift 2
354
+ ;;
355
+ --auto)
356
+ server_type="auto"
357
+ shift
358
+ ;;
359
+ --apache)
360
+ server_type="apache"
361
+ shift
362
+ ;;
363
+ --nginx)
364
+ server_type="nginx"
365
+ shift
366
+ ;;
367
+ --ssl)
368
+ ssl_mode="yes"
369
+ shift
370
+ ;;
371
+ --no-ssl)
372
+ ssl_mode="no"
373
+ shift
374
+ ;;
375
+ --certbot-email)
376
+ [[ $# -ge 2 ]] || fail "Missing value for $1"
377
+ certbot_email="$2"
378
+ shift 2
379
+ ;;
380
+ --certbot-staging)
381
+ certbot_staging="true"
382
+ shift
383
+ ;;
384
+ --yes|-y)
385
+ assume_yes="true"
386
+ shift
387
+ ;;
388
+ --dry-run)
389
+ dry_run="true"
390
+ shift
391
+ ;;
392
+ -h|--help)
393
+ print_usage
394
+ exit 0
395
+ ;;
396
+ *)
397
+ fail "Invalid option: $1"
398
+ ;;
399
+ esac
400
+ done
401
+
402
+ [[ -n "${domain_name}" ]] || fail "You must provide -d|--domain."
403
+
404
+ server_type="${server_type,,}"
405
+
406
+ validate_domain
407
+ validate_requested_server_type
408
+ prompt_for_server_type_if_needed
409
+ resolve_auto_server_type
410
+ validate_server_type
411
+ decide_ssl_mode
412
+ resolve_certbot_identity
413
+ check_required_commands
414
+ warn_if_not_root
415
+
416
+ printf 'Domain: %s\n' "${domain_name}"
417
+ printf 'Server: %s\n' "${server_type}"
418
+ printf 'SSL: %s\n' "${should_run_ssl}"
419
+ if [[ -n "${certbot_email}" ]]; then
420
+ printf 'Certbot email: %s\n' "${certbot_email}"
421
+ fi
422
+ if [[ "${dry_run}" == "true" ]]; then
423
+ printf 'Dry run mode enabled.\n'
424
+ fi
425
+
426
+ create_site_directory
427
+ create_server_config
428
+
429
+ if [[ "${server_type}" == "apache" ]]; then
430
+ enable_apache_site
431
+ else
432
+ enable_nginx_site
433
+ fi
434
+
435
+ if [[ "${should_run_ssl}" == "true" ]]; then
436
+ run_certbot
437
+ fi
438
+
439
+ printf 'Setup complete for %s (%s)\n' "${domain_name}" "${server_type}"
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [[ "$(uname -s)" != "Linux" ]]; then
5
+ echo "Skipping Linux smoke tests on non-Linux host."
6
+ exit 0
7
+ fi
8
+
9
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
10
+ CLI="${ROOT_DIR}/script/create-web-site.sh"
11
+
12
+ fail() {
13
+ echo "FAIL: $*" >&2
14
+ exit 1
15
+ }
16
+
17
+ assert_file_exists() {
18
+ [[ -f "$1" ]] || fail "Expected file to exist: $1"
19
+ }
20
+
21
+ assert_symlink_exists() {
22
+ [[ -L "$1" ]] || fail "Expected symlink to exist: $1"
23
+ }
24
+
25
+ assert_contains() {
26
+ local file="$1"
27
+ local pattern="$2"
28
+ grep -F "$pattern" "$file" >/dev/null || fail "Expected '$pattern' in $file"
29
+ }
30
+
31
+ create_mock_bin() {
32
+ local dir="$1"
33
+
34
+ cat >"${dir}/systemctl" <<'EOF'
35
+ #!/usr/bin/env bash
36
+ echo "systemctl $*" >> "${MOCK_LOG}"
37
+ EOF
38
+
39
+ cat >"${dir}/a2ensite" <<'EOF'
40
+ #!/usr/bin/env bash
41
+ echo "a2ensite $*" >> "${MOCK_LOG}"
42
+ EOF
43
+
44
+ cat >"${dir}/certbot" <<'EOF'
45
+ #!/usr/bin/env bash
46
+ echo "certbot $*" >> "${MOCK_LOG}"
47
+ EOF
48
+
49
+ chmod +x "${dir}/systemctl" "${dir}/a2ensite" "${dir}/certbot"
50
+ }
51
+
52
+ main() {
53
+ chmod +x "${CLI}"
54
+
55
+ SANDBOX="$(mktemp -d)"
56
+ trap 'rm -rf "${SANDBOX}"' EXIT
57
+
58
+ local mock_bin="${SANDBOX}/mock-bin"
59
+ mkdir -p "${mock_bin}"
60
+ create_mock_bin "${mock_bin}"
61
+
62
+ export PATH="${mock_bin}:${PATH}"
63
+ export MOCK_LOG="${SANDBOX}/mock.log"
64
+ : > "${MOCK_LOG}"
65
+
66
+ export WEB_SITE_ROOT="${SANDBOX}/www"
67
+ export WEB_SITE_APACHE_AVAILABLE_DIR="${SANDBOX}/apache/sites-available"
68
+ export WEB_SITE_NGINX_AVAILABLE_DIR="${SANDBOX}/nginx/sites-available"
69
+ export WEB_SITE_NGINX_ENABLED_DIR="${SANDBOX}/nginx/sites-enabled"
70
+ export WEB_SITE_SYSTEMCTL_BIN="systemctl"
71
+ export WEB_SITE_A2ENSITE_BIN="a2ensite"
72
+ export WEB_SITE_CERTBOT_BIN="certbot"
73
+
74
+ bash "${CLI}" --server apache --domain linux-apache.test --ssl --certbot-email ops@linux-apache.test
75
+ bash "${CLI}" --server nginx --domain linux-nginx.test --no-ssl
76
+
77
+ assert_file_exists "${WEB_SITE_APACHE_AVAILABLE_DIR}/linux-apache.test.conf"
78
+ assert_file_exists "${WEB_SITE_NGINX_AVAILABLE_DIR}/linux-nginx.test.conf"
79
+ assert_symlink_exists "${WEB_SITE_NGINX_ENABLED_DIR}/linux-nginx.test.conf"
80
+ assert_contains "${MOCK_LOG}" "certbot --apache -d linux-apache.test -d www.linux-apache.test --redirect --non-interactive --agree-tos --email ops@linux-apache.test"
81
+ assert_contains "${MOCK_LOG}" "systemctl restart apache2"
82
+ assert_contains "${MOCK_LOG}" "systemctl restart nginx"
83
+
84
+ echo "Linux smoke tests passed."
85
+ }
86
+
87
+ main "$@"
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ CLI="${ROOT_DIR}/script/create-web-site.sh"
6
+ APACHE_WRAPPER="${ROOT_DIR}/script/create-apache-site.sh"
7
+ NGINX_WRAPPER="${ROOT_DIR}/script/create-nginx-site.sh"
8
+ SANDBOX=""
9
+
10
+ fail() {
11
+ echo "FAIL: $*" >&2
12
+ exit 1
13
+ }
14
+
15
+ assert_file_exists() {
16
+ [[ -f "$1" ]] || fail "Expected file to exist: $1"
17
+ }
18
+
19
+ assert_symlink_exists() {
20
+ [[ -L "$1" ]] || fail "Expected symlink to exist: $1"
21
+ }
22
+
23
+ assert_contains() {
24
+ local file="$1"
25
+ local pattern="$2"
26
+ grep -F "$pattern" "$file" >/dev/null || fail "Expected '$pattern' in $file"
27
+ }
28
+
29
+ create_mock_bin() {
30
+ local dir="$1"
31
+
32
+ cat >"${dir}/systemctl" <<'EOF'
33
+ #!/usr/bin/env bash
34
+ echo "systemctl $*" >> "${MOCK_LOG}"
35
+ EOF
36
+
37
+ cat >"${dir}/a2ensite" <<'EOF'
38
+ #!/usr/bin/env bash
39
+ echo "a2ensite $*" >> "${MOCK_LOG}"
40
+ EOF
41
+
42
+ cat >"${dir}/certbot" <<'EOF'
43
+ #!/usr/bin/env bash
44
+ echo "certbot $*" >> "${MOCK_LOG}"
45
+ EOF
46
+
47
+ chmod +x "${dir}/systemctl" "${dir}/a2ensite" "${dir}/certbot"
48
+ }
49
+
50
+ setup_env() {
51
+ local sandbox="$1"
52
+
53
+ export WEB_SITE_ROOT="${sandbox}/www"
54
+ export WEB_SITE_APACHE_AVAILABLE_DIR="${sandbox}/apache/sites-available"
55
+ export WEB_SITE_NGINX_AVAILABLE_DIR="${sandbox}/nginx/sites-available"
56
+ export WEB_SITE_NGINX_ENABLED_DIR="${sandbox}/nginx/sites-enabled"
57
+ export WEB_SITE_SYSTEMCTL_BIN="systemctl"
58
+ export WEB_SITE_A2ENSITE_BIN="a2ensite"
59
+ export WEB_SITE_CERTBOT_BIN="certbot"
60
+ }
61
+
62
+ test_apache_no_ssl() {
63
+ local sandbox="$1"
64
+ : > "${MOCK_LOG}"
65
+ setup_env "${sandbox}"
66
+
67
+ bash "${CLI}" --server apache --domain example.com --no-ssl
68
+
69
+ assert_file_exists "${WEB_SITE_ROOT}/example.com/public_html/index.html"
70
+ assert_contains "${WEB_SITE_ROOT}/example.com/public_html/index.html" "example.com"
71
+ assert_file_exists "${WEB_SITE_APACHE_AVAILABLE_DIR}/example.com.conf"
72
+ assert_contains "${WEB_SITE_APACHE_AVAILABLE_DIR}/example.com.conf" "ServerName example.com"
73
+ assert_contains "${MOCK_LOG}" "a2ensite example.com.conf"
74
+ assert_contains "${MOCK_LOG}" "systemctl reload apache2"
75
+ assert_contains "${MOCK_LOG}" "systemctl restart apache2"
76
+ }
77
+
78
+ test_nginx_with_ssl() {
79
+ local sandbox="$1"
80
+ : > "${MOCK_LOG}"
81
+ setup_env "${sandbox}"
82
+
83
+ bash "${CLI}" --server nginx --domain example.net --ssl --certbot-email admin@example.net
84
+
85
+ assert_file_exists "${WEB_SITE_NGINX_AVAILABLE_DIR}/example.net.conf"
86
+ assert_contains "${WEB_SITE_NGINX_AVAILABLE_DIR}/example.net.conf" "server_name example.net www.example.net;"
87
+ assert_symlink_exists "${WEB_SITE_NGINX_ENABLED_DIR}/example.net.conf"
88
+ assert_contains "${MOCK_LOG}" "systemctl reload nginx"
89
+ assert_contains "${MOCK_LOG}" "systemctl restart nginx"
90
+ assert_contains "${MOCK_LOG}" "certbot --nginx -d example.net -d www.example.net --redirect --non-interactive --agree-tos --email admin@example.net"
91
+ }
92
+
93
+ test_wrappers() {
94
+ local sandbox="$1"
95
+ : > "${MOCK_LOG}"
96
+ setup_env "${sandbox}"
97
+
98
+ bash "${APACHE_WRAPPER}" --domain wrapped-apache.com --no-ssl
99
+ bash "${NGINX_WRAPPER}" --domain wrapped-nginx.com --no-ssl
100
+
101
+ assert_file_exists "${WEB_SITE_APACHE_AVAILABLE_DIR}/wrapped-apache.com.conf"
102
+ assert_file_exists "${WEB_SITE_NGINX_AVAILABLE_DIR}/wrapped-nginx.com.conf"
103
+ }
104
+
105
+ test_auto_server_mode() {
106
+ local sandbox="$1"
107
+ : > "${MOCK_LOG}"
108
+ setup_env "${sandbox}"
109
+
110
+ mkdir -p "${WEB_SITE_NGINX_AVAILABLE_DIR}" "${WEB_SITE_NGINX_ENABLED_DIR}"
111
+ export WEB_SITE_A2ENSITE_BIN="missing-a2ensite"
112
+
113
+ bash "${CLI}" --server auto --domain auto-mode.com --no-ssl
114
+ bash "${CLI}" --domain auto-default.com --no-ssl
115
+
116
+ assert_file_exists "${WEB_SITE_NGINX_AVAILABLE_DIR}/auto-mode.com.conf"
117
+ assert_file_exists "${WEB_SITE_NGINX_AVAILABLE_DIR}/auto-default.com.conf"
118
+ assert_contains "${MOCK_LOG}" "systemctl reload nginx"
119
+ }
120
+
121
+ main() {
122
+ chmod +x "${CLI}" "${APACHE_WRAPPER}" "${NGINX_WRAPPER}"
123
+
124
+ SANDBOX="$(mktemp -d)"
125
+ trap 'rm -rf "${SANDBOX}"' EXIT
126
+
127
+ local mock_bin="${SANDBOX}/mock-bin"
128
+ mkdir -p "${mock_bin}"
129
+ create_mock_bin "${mock_bin}"
130
+ export MOCK_LOG="${SANDBOX}/mock.log"
131
+ touch "${MOCK_LOG}"
132
+ export PATH="${mock_bin}:${PATH}"
133
+
134
+ test_apache_no_ssl "${SANDBOX}"
135
+ test_nginx_with_ssl "${SANDBOX}"
136
+ test_wrappers "${SANDBOX}"
137
+ test_auto_server_mode "${SANDBOX}"
138
+
139
+ echo "All tests passed."
140
+ }
141
+
142
+ main "$@"