Add webserver provisioning + vhost scripts, README, cheatsheet
- setup-webserver.sh: idempotent Ubuntu 24.04 LAMP provisioning (Apache event MPM + PHP 8.3-FPM + MariaDB + Node/Python, phpMyAdmin, Composer, Certbot, UFW, Fail2ban; optional components prompted/env-gated) - add-vhost.sh: add an Apache virtual host, optional DB + TLS - CHEATSHEET.md: day-to-day server CLI reference - README.md: setup instructions and env-var matrix Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# add-vhost.sh — Add an Apache virtual host on Ubuntu 24.04
|
||||
#
|
||||
# Hybrid: pass args to skip prompts, omit them to be asked.
|
||||
#
|
||||
# sudo ./add-vhost.sh # fully interactive
|
||||
# sudo ./add-vhost.sh example.com # domain set, rest prompted
|
||||
# sudo ./add-vhost.sh example.com /var/www/ex # domain + root set
|
||||
#
|
||||
# Non-interactive (CI / scripted) — set env vars, prompts auto-skip:
|
||||
# sudo DOMAIN=example.com MAKE_DB=yes RUN_TLS=yes ./add-vhost.sh
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
PHP_VER="8.3"
|
||||
|
||||
log() { printf '\n\033[1;32m==> %s\033[0m\n' "$*"; }
|
||||
warn() { printf '\033[1;33m!! %s\033[0m\n' "$*"; }
|
||||
die() { printf '\033[1;31mXX %s\033[0m\n' "$*" >&2; exit 1; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Run as root: sudo bash $0"
|
||||
|
||||
# Prompt only if stdin is a terminal; else rely on env/args (non-interactive).
|
||||
interactive() { [[ -t 0 ]]; }
|
||||
|
||||
# ask VAR "Question" "default" — sets VAR from env, else arg-passed, else prompt, else default
|
||||
ask() {
|
||||
local __var="$1" __q="$2" __def="${3:-}" __cur="${!1:-}"
|
||||
[[ -n "$__cur" ]] && return # already set (env or earlier arg)
|
||||
if interactive; then
|
||||
local __ans
|
||||
read -rp "$__q${__def:+ [$__def]}: " __ans
|
||||
printf -v "$__var" '%s' "${__ans:-$__def}"
|
||||
else
|
||||
printf -v "$__var" '%s' "$__def"
|
||||
fi
|
||||
}
|
||||
|
||||
ask_yn() { # ask_yn VAR "Question" default(yes/no)
|
||||
local __var="$1" __q="$2" __def="${3:-no}" __cur="${!1:-}"
|
||||
[[ -n "$__cur" ]] && return
|
||||
if interactive; then
|
||||
local __ans
|
||||
read -rp "$__q [$( [[ $__def == yes ]] && echo Y/n || echo y/N )]: " __ans
|
||||
__ans="${__ans:-$__def}"
|
||||
case "$__ans" in [Yy]*) printf -v "$__var" yes;; *) printf -v "$__var" no;; esac
|
||||
else
|
||||
printf -v "$__var" '%s' "$__def"
|
||||
fi
|
||||
}
|
||||
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
# Gather inputs: positional args win, then env, then prompt
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
DOMAIN="${DOMAIN:-${1:-}}"
|
||||
WEB_ROOT="${WEB_ROOT:-${2:-}}"
|
||||
|
||||
ask DOMAIN "Domain (e.g. example.com)"
|
||||
[[ -n "$DOMAIN" ]] || die "Domain required"
|
||||
# Basic sanity: letters/digits/dots/hyphens only
|
||||
[[ "$DOMAIN" =~ ^[A-Za-z0-9.-]+$ ]] || die "Invalid domain: $DOMAIN"
|
||||
|
||||
ask WEB_ROOT "Web root" "/var/www/${DOMAIN}"
|
||||
ask ADMIN_EMAIL "Admin email (for TLS)" "admin@${DOMAIN}"
|
||||
ask_yn MAKE_DB "Create matching MySQL database + user?" no
|
||||
ask_yn RUN_TLS "Request Let's Encrypt TLS now (DNS must point here)?" no
|
||||
|
||||
VHOST_FILE="/etc/apache2/sites-available/${DOMAIN}.conf"
|
||||
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
log "Web root: ${WEB_ROOT}"
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
mkdir -p "$WEB_ROOT"
|
||||
if [[ ! -f "${WEB_ROOT}/index.html" && -z "$(ls -A "$WEB_ROOT" 2>/dev/null)" ]]; then
|
||||
echo "<h1>${DOMAIN} works</h1>" > "${WEB_ROOT}/index.html"
|
||||
fi
|
||||
chown -R www-data:www-data "$WEB_ROOT"
|
||||
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
log "Write vhost: ${VHOST_FILE}"
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
[[ -f "$VHOST_FILE" ]] && warn "Overwriting existing ${VHOST_FILE}"
|
||||
cat > "$VHOST_FILE" <<EOF
|
||||
<VirtualHost *:80>
|
||||
ServerName ${DOMAIN}
|
||||
ServerAlias www.${DOMAIN}
|
||||
DocumentRoot ${WEB_ROOT}
|
||||
|
||||
<Directory ${WEB_ROOT}>
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog \${APACHE_LOG_DIR}/${DOMAIN}-error.log
|
||||
CustomLog \${APACHE_LOG_DIR}/${DOMAIN}-access.log combined
|
||||
</VirtualHost>
|
||||
EOF
|
||||
|
||||
a2ensite -q "${DOMAIN}.conf"
|
||||
apache2ctl configtest || die "Apache config test failed — fix before reload"
|
||||
systemctl reload apache2
|
||||
log "Site enabled + Apache reloaded"
|
||||
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$MAKE_DB" == "yes" ]]; then
|
||||
log "Create database + user"
|
||||
# DB name from domain: example.com -> example_com
|
||||
DB_NAME="$(echo "$DOMAIN" | tr '.-' '__')"
|
||||
DB_USER="${DB_NAME:0:32}" # MariaDB user max 32 chars (10.4+ allows 80, stay safe)
|
||||
# Generate random password
|
||||
DB_PASS="$(openssl rand -base64 18 | tr -d '/+=' | head -c 20)"
|
||||
|
||||
mariadb <<SQL
|
||||
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASS}';
|
||||
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_USER}'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
# Save creds to a root-only file next to web root
|
||||
CRED_FILE="/root/${DOMAIN}.db-credentials.txt"
|
||||
cat > "$CRED_FILE" <<EOF
|
||||
Domain : ${DOMAIN}
|
||||
Database : ${DB_NAME}
|
||||
Username : ${DB_USER}
|
||||
Password : ${DB_PASS}
|
||||
Host : localhost
|
||||
EOF
|
||||
chmod 600 "$CRED_FILE"
|
||||
warn "DB created. Credentials saved: ${CRED_FILE} (root-only)"
|
||||
echo " DB=${DB_NAME} USER=${DB_USER} PASS=${DB_PASS}"
|
||||
fi
|
||||
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
if [[ "$RUN_TLS" == "yes" ]]; then
|
||||
log "Request TLS certificate (certbot)"
|
||||
if command -v certbot >/dev/null; then
|
||||
certbot --apache -d "$DOMAIN" -d "www.${DOMAIN}" \
|
||||
-m "$ADMIN_EMAIL" --agree-tos --redirect -n \
|
||||
|| warn "Certbot failed — check DNS points to this server, retry manually"
|
||||
else
|
||||
warn "certbot not installed. Install: apt install certbot python3-certbot-apache"
|
||||
fi
|
||||
fi
|
||||
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
log "Done: ${DOMAIN}"
|
||||
#─────────────────────────────────────────────────────────────────────────────
|
||||
cat <<EOF
|
||||
|
||||
Domain : http://${DOMAIN}$( [[ $RUN_TLS == yes ]] && echo " (https enabled)" )
|
||||
Web root : ${WEB_ROOT}
|
||||
Vhost : ${VHOST_FILE}
|
||||
Logs : /var/log/apache2/${DOMAIN}-{error,access}.log
|
||||
$( [[ $MAKE_DB == yes ]] && echo " DB creds : /root/${DOMAIN}.db-credentials.txt" )
|
||||
|
||||
No TLS yet? After DNS points here:
|
||||
certbot --apache -d ${DOMAIN} -d www.${DOMAIN} -m ${ADMIN_EMAIL} --agree-tos --redirect
|
||||
EOF
|
||||
Reference in New Issue
Block a user