learning-website-framework1-8 Exercise 1: Generate Virtual Hosts and the Certificate Command ========================================================================================= Generate, from the site map, one Apache virtual host per site and the single certbot command that covers every name. Check the result: unique names and document roots, balanced tags, valid subdomain labels, and every vhost name present in the certificate command. The layout follows the live server's existing setup (Setting Up a Web Server on Debian: Apache, a virtual host file in sites-available, Certbot). Each site gets /var/www//current, a link that points at the live release (Exercise 2). Needs sitemap.py from Learning Website: Framework & Architecture 5. Save as vhosts.py: import re from sitemap import SITES DOMAIN = "osztromok.com" WEB_ROOT = "/var/www" VHOST = """# /etc/apache2/sites-available/{site}.{domain}.conf ServerAdmin webmaster@{domain} ServerName {site}.{domain} DocumentRoot {root}/{site}/current Options -Indexes +FollowSymLinks AllowOverride None Require all granted ErrorLog ${{APACHE_LOG_DIR}}/{site}-error.log CustomLog ${{APACHE_LOG_DIR}}/{site}-access.log combined """ def vhost(site): return VHOST.format(site=site, domain=DOMAIN, root=WEB_ROOT) def certbot_command(sites, include_root=True): names = ([DOMAIN, "www." + DOMAIN] if include_root else []) + [f"{s}.{DOMAIN}" for s in sites] return "sudo certbot --apache " + " ".join(f"-d {n}" for n in names), names def check(sites): problems = [] confs = {s: vhost(s) for s in sites} names = [re.search(r"ServerName\s+(\S+)", c).group(1) for c in confs.values()] roots = [re.search(r"DocumentRoot\s+(\S+)", c).group(1) for c in confs.values()] if len(set(names)) != len(names): problems.append("duplicate ServerName") if len(set(roots)) != len(roots): problems.append("duplicate DocumentRoot") for s, c in confs.items(): if c.count(""): problems.append(f"{s}: unbalanced VirtualHost") if not re.fullmatch(r"[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?", s): problems.append(f"{s}: not a valid subdomain label") cmd, cert_names = certbot_command(sites) for n in names: if n not in cert_names: problems.append(f"{n} is not covered by the certificate command") return problems if __name__ == "__main__": sites = list(SITES) print(vhost("languages")) cmd, names = certbot_command(sites) print(cmd) print() print(f"{len(sites)} sites, {len(names)} names on one certificate") problems = check(sites) print("checks:", "all passed" if not problems else problems) Run it: python vhosts.py Output (checked by running it): # /etc/apache2/sites-available/languages.osztromok.com.conf ServerAdmin webmaster@osztromok.com ServerName languages.osztromok.com DocumentRoot /var/www/languages/current Options -Indexes +FollowSymLinks AllowOverride None Require all granted ErrorLog ${APACHE_LOG_DIR}/languages-error.log CustomLog ${APACHE_LOG_DIR}/languages-access.log combined sudo certbot --apache -d osztromok.com -d www.osztromok.com -d languages.osztromok.com -d webdevelopment.osztromok.com -d programming.osztromok.com -d systems.osztromok.com -d ai.osztromok.com -d humanities.osztromok.com -d lifeskills.osztromok.com -d creative.osztromok.com 8 sites, 10 names on one certificate checks: all passed Notes ----- - AllowOverride is None because the new builds need no .htaccess; every rule lives in the virtual host (faster, and it cannot be overridden by a stray file). The old site used AllowOverride All and an .htaccess. - One certificate lists all ten names (the root, www, and the eight sites). Let's Encrypt allows many names on one certificate, and the Certbot method the server already uses (an HTTP challenge) works for each of them, so no DNS automation is needed. Adding a site later means re-running the command with the extra -d name. A wildcard certificate (*.osztromok.com) would avoid that, but it needs the DNS challenge. - This checker tests structure only. apache2ctl configtest was NOT run here (Apache is not installed on this machine). Run it on the server before every reload, then a2ensite and systemctl reload apache2. - Apache serves a request for an unknown host name from the first virtual host it loads for that address and port. Keep a deliberate default (the existing 000-default.conf loads first) so a typo in DNS never shows the wrong site. WHY THIS WORKS AS AN ANSWER --------------------------- Virtual hosts are identical except for the name, so they are generated from the one map instead of copied by hand. The checks turn the typical mistakes (a duplicated name, a name missing from the certificate) into a failed script rather than a certificate error in front of a visitor.