#!/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 "

${DOMAIN} works

" > "${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" < ServerName ${DOMAIN} ServerAlias www.${DOMAIN} DocumentRoot ${WEB_ROOT} Options -Indexes +FollowSymLinks AllowOverride All Require all granted ErrorLog \${APACHE_LOG_DIR}/${DOMAIN}-error.log CustomLog \${APACHE_LOG_DIR}/${DOMAIN}-access.log combined 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 < "$CRED_FILE" </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 <