# Custom Services via VirtualHost Files This guide explains how to add custom services to YAHLP using VirtualHost configuration files without modifying the core application. ## Overview YAHLP supports adding optional services via Apache VirtualHost files. When you add a vhost file to the configuration directory: 1. **Automatic SSL certificates** are generated via Let's Encrypt 2. **VirtualHost is enabled** and loaded by Apache 3. **Authentication is applied** (optional, configurable per-service) 4. **Service is accessible** at your custom subdomain This feature allows you to extend YAHLP with any custom application (e.g., Keeper Automator, Overseerr, custom dashboards) without forking the codebase. ## Quick Start ### 1. Create a VirtualHost File Create a file in `/etc/yahlp/` with **"vhost" in the filename**: ``` /etc/yahlp/keeper_automator_vhost.conf /etc/yahlp/custom_app_vhost.conf /etc/yahlp/subdomain_vhost.conf ``` **Filename requirements:** - Must contain the word `vhost` (case-insensitive) - Must end with `.conf` extension - Examples: `myapp-vhost.conf`, `custom_vhost.conf`, `app_vhost_v2.conf` ✅ - Non-examples: `myapp.conf`, `vhost-template.bak`, `myapp.backup` ❌ ### 2. Add VirtualHost Configuration Minimal example for Keeper Automator: ```apache ServerName keeper-automator.yourdomain.com DocumentRoot /var/www/html # HTTP redirect to HTTPS RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # SSL configuration (auto-managed by YAHLP) SSLEngine on SSLCertificateFile /etc/letsencrypt/live/keeper-automator.yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/keeper-automator.yourdomain.com/privkey.pem # Proxy to backend service ProxyPreserveHost On ProxyPass / http://keeper-automator:8080/ ProxyPassReverse / http://keeper-automator:8080/ # Optional: Bypass authentication AuthType None Require all granted ``` ### 3. Restart YAHLP When the container starts: - Scans for files with "vhost" in the filename - Extracts domain names from VirtualHost ServerName directives - Requests SSL certificates from Let's Encrypt - Enables the vhost files - Apache loads the configuration ## VirtualHost File Format ### Full Example with Authentication ```apache ServerName myservice.yourdomain.com DocumentRoot /var/www/html # HTTP to HTTPS redirect RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # SSL Configuration SSLEngine on SSLCertificateFile /etc/letsencrypt/live/myservice.yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/myservice.yourdomain.com/privkey.pem SSLProtocols all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!MD5 SSLHonorCipherOrder on # Backend proxy ProxyPreserveHost On ProxyPass / http://myservice:8080/ ProxyPassReverse / http://myservice:8080/ # IMPORTANT: Bypass OIDC authentication # The main YAHLP OIDC module may interfere with custom vhosts AuthType None Require all granted ``` ### Key Configuration Points **1. ServerName Directive** ```apache ServerName myservice.yourdomain.com ``` - Must use full domain name (YAHLP extracts this for SSL certificate generation) - Only one ServerName per vhost - Used to determine certificate domain **2. SSL Configuration** ```apache SSLCertificateFile /etc/letsencrypt/live/myservice.yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/myservice.yourdomain.com/privkey.pem ``` - Paths follow Let's Encrypt standard structure - Certificates are auto-generated and renewed - Use the same domain as ServerName **3. Proxy Rules (for web apps)** ```apache ProxyPreserveHost On ProxyPass / http://backend-service:port/ ProxyPassReverse / http://backend-service:port/ ``` - ProxyPreserveHost maintains Host header - ProxyPass routes requests to backend - ProxyPassReverse maintains redirect headers **4. Authentication Bypass (IMPORTANT)** ```apache AuthType None Require all granted ``` - Disables OIDC authentication for this vhost - Allows unauthenticated access - Remove or modify if you want authentication ## Examples ### Keeper Automator ```apache ServerName keeper-automator.yourdomain.com DocumentRoot /var/www/html RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] SSLEngine on SSLCertificateFile /etc/letsencrypt/live/keeper-automator.yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/keeper-automator.yourdomain.com/privkey.pem SSLProtocols all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!MD5 ProxyPreserveHost On ProxyPass / http://keeper-automator:8080/ ProxyPassReverse / http://keeper-automator:8080/ AuthType None Require all granted ``` ### Overseerr (with authentication) ```apache ServerName overseerr.yourdomain.com DocumentRoot /var/www/html RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] SSLEngine on SSLCertificateFile /etc/letsencrypt/live/overseerr.yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/overseerr.yourdomain.com/privkey.pem SSLProtocols all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!MD5 ProxyPreserveHost On ProxyPass / http://overseerr:5055/ ProxyPassReverse / http://overseerr:5055/ # Use YAHLP authentication # Include auth config based on your AUTHTYPE Include /etc/apache2/conf-available/auth-google-protect.conf ``` ### Static Content Service ```apache ServerName static.yourdomain.com DocumentRoot /var/www/static-content RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] SSLEngine on SSLCertificateFile /etc/letsencrypt/live/static.yourdomain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/static.yourdomain.com/privkey.pem SSLProtocols all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite HIGH:!aNULL:!MD5 Options Indexes FollowSymLinks AllowOverride All Require all granted ``` ## Mounting VirtualHost Files ### Docker Compose ```yaml services: yahlp: image: auskento/yahlp:latest environment: - DOMAIN=yourdomain.com - ACCESS_MODE=public # ... other environment variables volumes: - yahlp_config:/etc/yahlp # Mount custom vhost files - ./config/keeper_automator_vhost.conf:/etc/yahlp/keeper_automator_vhost.conf:ro - ./config/custom_vhost.conf:/etc/yahlp/custom_vhost.conf:ro volumes: yahlp_config: ``` ### Docker Volume ```yaml services: yahlp: image: auskento/yahlp:latest volumes: - yahlp_config:/etc/yahlp # Place vhost files in: # yahlp_config/keeper_automator_vhost.conf # yahlp_config/custom_vhost.conf ``` ### Unraid Template In Unraid's Docker settings, add path mappings: ``` Container Path: /etc/yahlp/keeper_automator_vhost.conf Host Path: /mnt/user/appdata/yahlp/keeper_automator_vhost.conf ``` ## SSL Certificate Management ### Automatic Generation When YAHLP starts: 1. Scans for files containing "vhost" in the filename 2. Extracts `ServerName` directives from each file 3. Checks if certificates already exist 4. Requests new certificates for missing domains 5. Enables all vhost files with Apache's `a2ensite` ### Certificate Renewal Certificates are automatically renewed via cron: - Runs daily at 2 AM (container timezone) - Uses `certbot renew` for existing certificates - Pre-renewal: Apache is stopped to free port 443 - Post-renewal: Apache is restarted ### Staging vs Production If `DASHBOARD_TEST=true` (staging certificates): - Custom vhost certificates also use Let's Encrypt staging server - Not trusted by browsers (for testing only) - Unlimited rate limits for testing When switching `DASHBOARD_TEST=false` (production): - Staging certificates are automatically removed - New production certificates are requested - Force renewal ensures valid certificates are issued ### Troubleshooting Certificates **Certificate request failed:** ```bash docker logs yahlp-container | grep "Certificate request" ``` **Check certificate status:** ```bash docker exec yahlp-container certbot certificates ``` **Manually renew a certificate:** ```bash docker exec yahlp-container certbot renew --force-renewal ``` ## Authentication ### Bypass (No Authentication) ```apache AuthType None Require all granted ``` Use this if the backend service handles its own authentication. ### Basic Authentication ```apache AuthType Basic AuthName "Custom Service" AuthUserFile /etc/apache2/.htpasswd Require valid-user ``` Create `.htpasswd`: ```bash docker exec yahlp-container htpasswd -c /etc/apache2/.htpasswd username ``` ### Google OAuth (OIDC) ```apache AuthType openid-connect Require claim email:your-email@gmail.com ``` Requires AUTHTYPE=google configured in YAHLP. ### Azure Entra (OIDC) ```apache AuthType openid-connect Require claim sub:user-object-id ``` Requires AUTHTYPE=entra configured in YAHLP. ## Troubleshooting ### VirtualHost Not Loading **Symptom:** Service inaccessible, certificate not generated **Diagnosis:** 1. Check filename contains "vhost": `ls /etc/yahlp/*vhost*` 2. Verify Apache syntax: `docker exec yahlp-container apache2ctl configtest` 3. Check logs: `docker logs yahlp-container | grep -i vhost` **Solution:** - Rename file to include "vhost" in name - Fix Apache syntax errors in config - Restart container: `docker restart yahlp-container` ### SSL Certificate Not Generated **Symptom:** HTTPS returns untrusted certificate error **Diagnosis:** 1. Check certificate status: `docker exec yahlp-container certbot certificates` 2. Check logs: `docker logs yahlp-container | grep -i certificate` 3. Verify DNS: `nslookup keeper-automator.yourdomain.com` **Solutions:** - Verify domain resolves to correct IP - Ensure port 80 is accessible (needed for Let's Encrypt validation) - Check DNS propagation (may take time) - Manually trigger cert request: `docker exec yahlp-container /usr/local/bin/docker-entrypoint.sh` ### Service Inaccessible After Setup **Symptom:** Domain loads but shows error/blank page **Diagnosis:** 1. Test backend service: `docker exec yahlp-container curl http://keeper-automator:8080` 2. Check Apache error logs: `docker exec yahlp-container tail /var/log/apache2/error.log` 3. Verify proxy rules in vhost file **Solutions:** - Confirm backend service is running and accessible - Check backend container network connectivity - Verify proxy ports and paths match backend service - Add debug headers to proxy: `Header set X-Forwarded-Proto "https"` ### Authentication Bypass Not Working **Symptom:** Still prompted for login, can't access service **Cause:** Default YAHLP OIDC module checking cookie domain globally **Solution:** Ensure LocationMatch block is present: ```apache AuthType None Require all granted ``` The exact path `^/(.*)$` is important - it matches all requests to bypass OIDC. ### Certificate Renewal Issues **Symptom:** Certificate expiry warnings, manual renewal required **Diagnosis:** ```bash docker logs yahlp-container | grep -i renewal ``` **Solutions:** - Check cron is running: `docker exec yahlp-container ps aux | grep cron` - Verify Apache can be stopped/started - Manual renewal: `docker exec yahlp-container certbot renew --force-renewal` ## File Locations | Item | Location | |------|----------| | VirtualHost files | `/etc/yahlp/*.conf` | | SSL Certificates | `/etc/letsencrypt/live/yourdomain.com/` | | Apache logs | `/var/log/apache2/error.log` | | Certbot logs | `/var/log/letsencrypt/letsencrypt.log` | | Renewal config | `/etc/letsencrypt/renewal/yourdomain.com.conf` | ## Best Practices 1. **Naming:** - Use descriptive names: `keeper-automator_vhost.conf`, not `temp.conf` - Always include "vhost" in filename 2. **Security:** - Use HTTPS for all vhosts (automatically configured) - Enable authentication when accessing sensitive data - Test with `DASHBOARD_TEST=true` before production 3. **Backups:** - Keep vhost files in version control - Mount config as persistent volume - Regular backups of `/etc/yahlp/` 4. **Testing:** - Test in staging mode first (`DASHBOARD_TEST=true`) - Verify DNS before requesting certificates - Check backend service connectivity 5. **Documentation:** - Keep comments in vhost files explaining configuration - Document backend service requirements - Note any special authentication needs ## Deployment Examples ### Complete Docker Compose with Custom Service ```yaml version: '3.8' services: yahlp: image: auskento/yahlp:latest container_name: yahlp environment: - DOMAIN=yourdomain.com - ACCESS_MODE=public - EMAIL=admin@yourdomain.com - DASHBOARD_TEST=false - AUTHTYPE=none - DASHBOARD_NAME=My Portal volumes: - yahlp_config:/etc/yahlp - ./keeper_automator_vhost.conf:/etc/yahlp/keeper_automator_vhost.conf:ro ports: - "80:80" - "443:443" - "3000:3000" restart: unless-stopped keeper-automator: image: keeper/automator:latest container_name: keeper-automator environment: - PORT=8080 restart: unless-stopped volumes: yahlp_config: ``` ## See Also - [Configuration Reference](configuration.md) - YAHLP environment variables - [Security](security.md) - SSL/TLS and authentication setup - [Services](services.md) - Built-in service configuration