#!/bin/sh # Create SSL directory if it doesn't exist mkdir -p /etc/nginx/ssl # Select the appropriate nginx configuration based on FORCE_HTTPS_REDIRECT # Default to true (nginx-443.conf) for backward compatibility # Only disable redirect if explicitly set to "false" if [ "${FORCE_HTTPS_REDIRECT:-true}" = "false" ]; then echo "Using nginx-80-443.conf (HTTP and HTTPS without redirect)" cp /etc/nginx/nginx-80-443.conf /etc/nginx/nginx.conf else echo "Using nginx-443.conf (HTTP to HTTPS redirect enabled - default)" cp /etc/nginx/nginx-443.conf /etc/nginx/nginx.conf fi # Substitute MAX_UPLOAD_SIZE placeholder in the active nginx config. # Default to 100MB when unset (suitable for all-in-one images without explicit config). MAX_UPLOAD_SIZE_MB_VALUE="${MAX_UPLOAD_SIZE_MB:-100}" sed -i "s|__MAX_UPLOAD_SIZE__|${MAX_UPLOAD_SIZE_MB_VALUE}|g" /etc/nginx/nginx.conf # Build the geo-block rules for the /admin IP allowlist from ADMIN_IP_ALLOWLIST. # Empty -> "default 0;" (everyone allowed). "private" -> default-deny with # loopback + RFC1918 carved out. Anything else is treated as a comma-separated # list of CIDRs/IPs (loopback is always allowed). Tokens that don't look like # an IPv4/IPv6 address or CIDR are skipped with a warning so a typo can't # crash-loop nginx; if no valid tokens remain we fail closed (deny everything # except loopback). When the resulting $admin_block is 1, the /admin location # quietly routes the request to the client app via @admin_fallback rather than # emitting a 403/404. is_valid_cidr() { case "$1" in *:*) printf '%s' "$1" | grep -Eq '^[0-9a-fA-F:]+(/[0-9]{1,3})?$' ;; *) printf '%s' "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$' ;; esac } build_admin_ip_geo_rules() { local raw="${ADMIN_IP_ALLOWLIST:-}" case "$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')" in "") printf '%s' "default 0;" ;; private) printf '%s' "default 1; 127.0.0.0/8 0; 10.0.0.0/8 0; 172.16.0.0/12 0; 192.168.0.0/16 0;" ;; *) local out="default 1; 127.0.0.0/8 0;" local valid=0 local IFS=',' for cidr in $raw; do cidr=$(printf '%s' "$cidr" | tr -d '[:space:]') [ -z "$cidr" ] && continue if is_valid_cidr "$cidr"; then out="$out $cidr 0;" valid=$((valid + 1)) else printf 'warning: ADMIN_IP_ALLOWLIST: ignoring invalid CIDR/IP %s\n' "$cidr" >&2 fi done if [ "$valid" -eq 0 ]; then printf 'warning: ADMIN_IP_ALLOWLIST has no valid entries; /admin will fall through to the client app for all non-loopback traffic\n' >&2 fi printf '%s' "$out" ;; esac } ADMIN_IP_GEO_RULES_VALUE=$(build_admin_ip_geo_rules) sed -i "s|__ADMIN_IP_GEO_RULES__|${ADMIN_IP_GEO_RULES_VALUE}|g" /etc/nginx/nginx.conf # Build the set_real_ip_from directives from TRUSTED_PROXIES. These tell nginx # which upstream proxies it should accept the X-Forwarded-For header from when # determining the real client IP. Empty -> RFC1918 (backwards-compatible # default). "none" -> no directives (raw $remote_addr is always used). Otherwise # treated as a comma-separated list of CIDRs/IPs; invalid tokens are skipped # with a warning so a typo can't crash-loop nginx, and if no valid tokens # remain we emit no directives (equivalent to "none"). build_trusted_proxies_directives() { local raw="${TRUSTED_PROXIES:-}" case "$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')" in "") printf '%s' "set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16;" ;; none) printf '%s' "" ;; *) local out="" local valid=0 local IFS=',' for cidr in $raw; do cidr=$(printf '%s' "$cidr" | tr -d '[:space:]') [ -z "$cidr" ] && continue if is_valid_cidr "$cidr"; then if [ -z "$out" ]; then out="set_real_ip_from $cidr;" else out="$out set_real_ip_from $cidr;" fi valid=$((valid + 1)) else printf 'warning: TRUSTED_PROXIES: ignoring invalid CIDR/IP %s\n' "$cidr" >&2 fi done if [ "$valid" -eq 0 ]; then printf 'warning: TRUSTED_PROXIES has no valid entries; X-Forwarded-For will be ignored from all upstreams\n' >&2 fi printf '%s' "$out" ;; esac } TRUSTED_PROXIES_VALUE=$(build_trusted_proxies_directives) sed -i "s|__TRUSTED_PROXIES__|${TRUSTED_PROXIES_VALUE}|g" /etc/nginx/nginx.conf # Function to check if certificate needs regeneration needs_cert_regeneration() { # If cert doesn't exist, need to generate if [ ! -f /etc/nginx/ssl/cert.pem ] || [ ! -f /etc/nginx/ssl/key.pem ]; then return 0 fi # Check if we have a hostname marker file if [ -f /etc/nginx/ssl/.hostname_marker ]; then STORED_HOSTNAME=$(cat /etc/nginx/ssl/.hostname_marker) if [ "$STORED_HOSTNAME" != "${HOSTNAME:-localhost}" ]; then echo "Hostname changed from '$STORED_HOSTNAME' to '${HOSTNAME:-localhost}', regenerating certificate..." return 0 fi else # No marker file, regenerate to be safe return 0 fi return 1 } # Function to check if a string is an IP address (IPv4 or IPv6) is_ip_address() { local value="$1" # Check for IPv4 pattern if echo "$value" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then # Validate each octet is <= 255 local valid=1 # Use a simple approach to split the IP address local o1=$(echo "$value" | cut -d. -f1) local o2=$(echo "$value" | cut -d. -f2) local o3=$(echo "$value" | cut -d. -f3) local o4=$(echo "$value" | cut -d. -f4) for octet in "$o1" "$o2" "$o3" "$o4"; do if [ "$octet" -gt 255 ]; then valid=0 break fi done if [ "$valid" -eq 1 ]; then return 0 # It's a valid IPv4 fi fi # Check for IPv6 pattern (simplified check) if echo "$value" | grep -qE '^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$|^::1$|^::$'; then return 0 # It's likely IPv6 fi return 1 # Not an IP address } # Generate self-signed SSL certificate if not exists or hostname changed if needs_cert_regeneration; then echo "Generating new SSL certificate (10 years validity)..." HOSTNAME_VALUE="${HOSTNAME:-localhost}" if [ "$HOSTNAME_VALUE" = "localhost" ] || [ -z "$HOSTNAME_VALUE" ]; then # Default localhost-only configuration openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/key.pem \ -out /etc/nginx/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" else # Determine if the hostname is an IP address or a DNS name if is_ip_address "$HOSTNAME_VALUE"; then # It's an IP address - use IP: prefix in SAN SAN_ENTRY="IP:${HOSTNAME_VALUE}" echo "Detected IP address: ${HOSTNAME_VALUE}" else # It's a DNS name - use DNS: prefix in SAN SAN_ENTRY="DNS:${HOSTNAME_VALUE}" echo "Detected hostname: ${HOSTNAME_VALUE}" fi # Generate certificate with the appropriate SAN entry openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/key.pem \ -out /etc/nginx/ssl/cert.pem \ -subj "/C=US/ST=State/L=City/O=AliasVault/CN=${HOSTNAME_VALUE}" \ -addext "subjectAltName=${SAN_ENTRY},DNS:localhost,IP:127.0.0.1" fi # Set proper permissions chmod 644 /etc/nginx/ssl/cert.pem chmod 600 /etc/nginx/ssl/key.pem # Store current hostname for change detection echo "${HOSTNAME:-localhost}" > /etc/nginx/ssl/.hostname_marker chmod 644 /etc/nginx/ssl/.hostname_marker fi # Create the appropriate SSL configuration based on LETSENCRYPT_ENABLED if [ "${LETSENCRYPT_ENABLED}" = "true" ]; then cat > /etc/nginx/ssl.conf << EOF ssl_certificate /etc/nginx/ssl-letsencrypt/live/${HOSTNAME}/fullchain.pem; ssl_certificate_key /etc/nginx/ssl-letsencrypt/live/${HOSTNAME}/privkey.pem; EOF else cat > /etc/nginx/ssl.conf << EOF ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; EOF fi # Start nginx and check if it started successfully nginx if [ $? -ne 0 ]; then echo "Failed to start nginx, exiting..." exit 1 fi # Start certificate watcher if Let's Encrypt is enabled if [ "${LETSENCRYPT_ENABLED}" = "true" ]; then echo "Starting certificate watcher for automatic nginx reload..." # Watch for changes and reload nginx while true; do # Watch the entire Let's Encrypt live directory for any certificate changes inotifywait -e modify,create,delete,move -r /etc/nginx/ssl-letsencrypt 2>/dev/null # Wait a moment for all certificate files to be written sleep 2 echo "Certificate change detected, reloading nginx..." nginx -s reload 2>&1 done & fi # Keep the container running and monitor nginx while true; do if ! pgrep nginx > /dev/null; then echo "Nginx is not running, exiting..." exit 1 fi sleep 10 done