#!/bin/sh /etc/rc.common # SPDX-License-Identifier: Apache-2.0 # CDN Cache Proxy Init Script - Squid Backend # Copyright (C) 2025 CyberMind.fr START=90 STOP=10 USE_PROCD=1 SQUID_CONF="/var/etc/cdn-cache-squid.conf" CACHE_DIR="/var/cache/cdn-squid" LOG_DIR="/var/log/cdn-cache" SSL_DB="/var/lib/ssl_db" CA_DIR="/etc/squid/ssl" CA_CERT="$CA_DIR/ca.pem" CA_KEY="$CA_DIR/ca.key" validate_section() { uci_load_validate cdn-cache main "$1" "$2" \ 'enabled:bool:0' \ 'cache_dir:string:/var/cache/cdn-squid' \ 'cache_size:uinteger:2048' \ 'max_object_size:uinteger:1024' \ 'cache_valid:uinteger:10080' \ 'listen_port:port:3128' \ 'transparent:bool:1' \ 'ssl_bump:bool:0' \ 'log_level:string:1' } # Generate CA certificate for SSL bumping generate_ca_cert() { mkdir -p "$CA_DIR" if [ ! -f "$CA_CERT" ] || [ ! -f "$CA_KEY" ]; then logger -t cdn-cache "Generating CA certificate for SSL bumping..." # Generate CA private key openssl genrsa -out "$CA_KEY" 4096 2>/dev/null # Generate self-signed CA certificate openssl req -new -x509 -days 3650 -key "$CA_KEY" -out "$CA_CERT" \ -subj "/C=FR/ST=Paris/L=Paris/O=SecuBox/OU=CDN-Cache/CN=SecuBox CDN Cache CA" 2>/dev/null # Set proper permissions chmod 600 "$CA_KEY" chmod 644 "$CA_CERT" chown squid:squid "$CA_KEY" "$CA_CERT" 2>/dev/null logger -t cdn-cache "CA certificate generated at $CA_CERT" fi } # Initialize SSL certificate database init_ssl_db() { if [ ! -d "$SSL_DB/certs" ]; then logger -t cdn-cache "Initializing SSL certificate database..." rm -rf "$SSL_DB" mkdir -p "$SSL_DB" # Note: Size must be in bytes, not MB /usr/lib/squid/security_file_certgen -s "$SSL_DB" -M 4194304 -c 2>/dev/null chown -R squid:squid "$SSL_DB" 2>/dev/null fi } generate_squid_config() { local cache_dir="$1" local cache_size="$2" local max_object="$3" local cache_valid="$4" local listen_port="$5" local transparent="$6" local ssl_bump="$7" mkdir -p "$(dirname $SQUID_CONF)" mkdir -p "$cache_dir" mkdir -p "$LOG_DIR" chown squid:squid "$cache_dir" "$LOG_DIR" 2>/dev/null # Build listen directives # Need both a regular port (for client configuration) and optionally intercept port local intercept_port=$((listen_port + 1)) local ssl_bump_port=$((listen_port + 2)) cat > "$SQUID_CONF" << EOF # CDN Cache Squid Configuration # Auto-generated by SecuBox - Do not edit manually # === NETWORK === # Regular forward proxy port http_port $listen_port EOF if [ "$transparent" = "1" ]; then cat >> "$SQUID_CONF" << EOF # Transparent proxy intercept port (HTTP) http_port $intercept_port intercept EOF fi # SSL Bump configuration for HTTPS caching if [ "$ssl_bump" = "1" ] && [ -f "$CA_CERT" ] && [ -f "$CA_KEY" ]; then cat >> "$SQUID_CONF" << EOF # SSL Bump (HTTPS intercept) port https_port $ssl_bump_port intercept ssl-bump \\ cert=$CA_CERT \\ key=$CA_KEY \\ generate-host-certificates=on \\ dynamic_cert_mem_cache_size=4MB # SSL certificate database (size in bytes) sslcrtd_program /usr/lib/squid/security_file_certgen -s $SSL_DB -M 4194304 sslcrtd_children 5 # SSL Bump ACLs acl step1 at_step SslBump1 acl step2 at_step SslBump2 acl step3 at_step SslBump3 # Sites to NOT intercept (security sensitive) acl ssl_exclude_domains ssl::server_name .paypal.com .stripe.com .braintreegateway.com acl ssl_exclude_domains ssl::server_name .bank .banking .hsbc.com .chase.com .wellsfargo.com acl ssl_exclude_domains ssl::server_name .google.com/accounts .accounts.google.com acl ssl_exclude_domains ssl::server_name .apple.com/id .icloud.com acl ssl_exclude_domains ssl::server_name .github.com .gitlab.com # Sites to bump (cacheable content) acl ssl_bump_domains ssl::server_name .windowsupdate.com .microsoft.com .officecdn.microsoft.com acl ssl_bump_domains ssl::server_name .steamcontent.com .steampowered.com .steamcdn-a.akamaihd.net acl ssl_bump_domains ssl::server_name .epicgames.com .epicgames-download1.akamaized.net acl ssl_bump_domains ssl::server_name .download.nvidia.com .cdn.jsdelivr.net .cdnjs.cloudflare.com acl ssl_bump_domains ssl::server_name .archive.ubuntu.com .security.ubuntu.com acl ssl_bump_domains ssl::server_name .downloads.openwrt.org # SSL bump rules ssl_bump peek step1 all ssl_bump splice ssl_exclude_domains ssl_bump bump ssl_bump_domains ssl_bump splice all EOF fi cat >> "$SQUID_CONF" << EOF visible_hostname cdn-cache.secubox.local # === ACCESS CONTROL === acl localnet src 10.0.0.0/8 acl localnet src 172.16.0.0/12 acl localnet src 192.168.0.0/16 acl localnet src fc00::/7 acl localnet src fe80::/10 acl SSL_ports port 443 acl Safe_ports port 80 21 443 70 210 280 488 591 777 1025-65535 acl CONNECT method CONNECT http_access deny !Safe_ports http_access deny CONNECT !SSL_ports http_access allow localhost manager http_access deny manager http_access allow localnet http_access allow localhost http_access deny all # === CACHE STORAGE === cache_dir ufs $cache_dir $cache_size 16 256 cache_mem 128 MB maximum_object_size ${max_object} MB minimum_object_size 0 KB cache_swap_low 90 cache_swap_high 95 # === CDN CONTENT REFRESH PATTERNS === # Windows Updates - cache for 7 days refresh_pattern -i windowsupdate.com/.*\.(exe|msu|cab|msi)$ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-store ignore-private refresh_pattern -i download.microsoft.com/.*\.(exe|msu|cab|msi)$ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-store ignore-private refresh_pattern -i officecdn.microsoft.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload # Linux Package Repositories refresh_pattern -i (deb|rpm|pkg\.tar\.(gz|xz|zst))$ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i archive\.ubuntu\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i security\.ubuntu\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i dl\.fedoraproject\.org/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i mirror\.centos\.org/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i downloads\.openwrt\.org/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload # Android/Play Store refresh_pattern -i play\.googleapis\.com/.*\.apk$ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i android\.clients\.google\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload # Steam/Gaming refresh_pattern -i \.steampowered\.com/depot/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-store ignore-private refresh_pattern -i steamcdn-a\.akamaihd\.net/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload ignore-no-store ignore-private refresh_pattern -i cdn\.cloudflare\.steamstatic\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i epicgames-download1\.akamaized\.net/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i origin-a\.akamaihd\.net/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i uplaypc-s-ubisoft\.cdn\.ubi\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload # Apple Updates refresh_pattern -i swcdn\.apple\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i swscan\.apple\.com/ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload refresh_pattern -i \.itunes\.apple\.com/.*\.(ipa|pkg)$ ${cache_valid} 100% 43200 override-expire override-lastmod reload-into-ims ignore-reload # Static Content CDNs refresh_pattern -i \.cloudfront\.net/.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ 1440 100% 10080 override-expire override-lastmod refresh_pattern -i cdn\.jsdelivr\.net/ 1440 100% 10080 override-expire override-lastmod refresh_pattern -i cdnjs\.cloudflare\.com/ 1440 100% 10080 override-expire override-lastmod refresh_pattern -i ajax\.googleapis\.com/ 1440 100% 10080 override-expire override-lastmod refresh_pattern -i fonts\.googleapis\.com/ 1440 100% 10080 override-expire override-lastmod refresh_pattern -i fonts\.gstatic\.com/ 1440 100% 10080 override-expire override-lastmod # General static assets refresh_pattern -i \.(js|css)(\?.*)?$ 1440 50% 10080 refresh_pattern -i \.(png|jpg|jpeg|gif|ico|svg|webp)(\?.*)?$ 1440 50% 10080 refresh_pattern -i \.(woff|woff2|ttf|eot|otf)(\?.*)?$ 4320 50% 43200 refresh_pattern -i \.(mp3|mp4|webm|ogg|flac)(\?.*)?$ 1440 50% 10080 refresh_pattern -i \.(zip|tar|gz|bz2|xz|7z|rar)(\?.*)?$ 1440 50% 10080 # Default patterns refresh_pattern ^ftp: 1440 20% 10080 refresh_pattern ^gopher: 1440 0% 1440 refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 refresh_pattern . 0 20% 4320 # === EXCLUSIONS (Never Cache) === # Banking/Financial - security acl nocache_banking dstdomain .paypal.com .stripe.com .braintreegateway.com acl nocache_banking dstdomain .visa.com .mastercard.com .americanexpress.com # Video Streaming - too large, DRM protected acl nocache_streaming dstdomain .netflix.com .nflxvideo.net acl nocache_streaming dstdomain .youtube.com .googlevideo.com .ytimg.com acl nocache_streaming dstdomain .twitch.tv .twitchcdn.net acl nocache_streaming dstdomain .hulu.com .hulustream.com acl nocache_streaming dstdomain .disneyplus.com .dssott.com acl nocache_streaming dstdomain .hbomax.com .max.com acl nocache_streaming dstdomain .primevideo.com .aiv-cdn.net # Real-time APIs acl nocache_realtime dstdomain .api.github.com .api.gitlab.com acl nocache_realtime dstdomain .amazonaws.com .azure.com cache deny nocache_banking cache deny nocache_streaming cache deny nocache_realtime # === LOGGING === access_log daemon:$LOG_DIR/access.log squid cache_log $LOG_DIR/cache.log cache_store_log none debug_options ALL,$log_level # === API FAILOVER === EOF # Add API failover configuration if enabled local api_enabled stale_if_error offline_mode collapsed_forwarding connect_timeout read_timeout api_enabled=$(uci -q get cdn-cache.api_failover.enabled || echo "1") stale_if_error=$(uci -q get cdn-cache.api_failover.stale_if_error || echo "86400") offline_mode=$(uci -q get cdn-cache.api_failover.offline_mode || echo "0") collapsed_forwarding=$(uci -q get cdn-cache.api_failover.collapsed_forwarding || echo "1") connect_timeout=$(uci -q get cdn-cache.api_failover.connect_timeout || echo "5") read_timeout=$(uci -q get cdn-cache.api_failover.read_timeout || echo "30") if [ "$api_enabled" = "1" ]; then cat >> "$SQUID_CONF" << EOF # API Failover - serve stale content on backend errors refresh_pattern -i /api/ 1 20% 4320 stale-if-error=$stale_if_error ignore-no-store refresh_pattern -i \.json\$ 1 20% 4320 stale-if-error=$stale_if_error EOF if [ "$collapsed_forwarding" = "1" ]; then cat >> "$SQUID_CONF" << EOF # Collapsed forwarding - combine duplicate requests during backend fetch collapsed_forwarding on EOF fi if [ "$offline_mode" = "1" ]; then cat >> "$SQUID_CONF" << EOF # Offline mode - serve stale content for all requests offline_mode on EOF fi fi cat >> "$SQUID_CONF" << EOF # === PERFORMANCE === dns_nameservers 8.8.8.8 8.8.4.4 connect_timeout $connect_timeout seconds read_timeout $read_timeout seconds request_timeout 60 seconds client_lifetime 1 day half_closed_clients off pconn_timeout 60 seconds quick_abort_min 0 KB quick_abort_max 0 KB quick_abort_pct 95 negative_ttl 30 seconds # === CACHE MANAGER === cache_mgr secubox@local cachemgr_passwd none all # === MISC === cache_effective_user squid coredump_dir /var/spool/squid pid_filename none shutdown_lifetime 3 seconds EOF } setup_transparent() { local listen_port="$1" local transparent="$2" local ssl_bump="$3" # Intercept ports local intercept_port=$((listen_port + 1)) local ssl_bump_port=$((listen_port + 2)) # Setup firewall redirect rules via UCI if [ "$transparent" = "1" ]; then # HTTP redirect rule uci -q delete firewall.cdn_cache_redirect uci set firewall.cdn_cache_redirect=redirect uci set firewall.cdn_cache_redirect.name='CDN-Cache-HTTP' uci set firewall.cdn_cache_redirect.src='lan' uci set firewall.cdn_cache_redirect.proto='tcp' uci set firewall.cdn_cache_redirect.src_dport='80' uci set firewall.cdn_cache_redirect.dest_port="$intercept_port" uci set firewall.cdn_cache_redirect.target='DNAT' uci set firewall.cdn_cache_redirect.enabled='1' logger -t cdn-cache "HTTP transparent proxy on port $intercept_port" else uci -q set firewall.cdn_cache_redirect.enabled='0' fi # HTTPS/SSL bump redirect rule if [ "$ssl_bump" = "1" ] && [ "$transparent" = "1" ]; then uci -q delete firewall.cdn_cache_ssl_redirect uci set firewall.cdn_cache_ssl_redirect=redirect uci set firewall.cdn_cache_ssl_redirect.name='CDN-Cache-HTTPS' uci set firewall.cdn_cache_ssl_redirect.src='lan' uci set firewall.cdn_cache_ssl_redirect.proto='tcp' uci set firewall.cdn_cache_ssl_redirect.src_dport='443' uci set firewall.cdn_cache_ssl_redirect.dest_port="$ssl_bump_port" uci set firewall.cdn_cache_ssl_redirect.target='DNAT' uci set firewall.cdn_cache_ssl_redirect.enabled='1' logger -t cdn-cache "HTTPS SSL bump on port $ssl_bump_port" else uci -q set firewall.cdn_cache_ssl_redirect.enabled='0' fi uci commit firewall /etc/init.d/firewall reload 2>/dev/null } remove_transparent() { # Disable the firewall redirect rules uci -q set firewall.cdn_cache_redirect.enabled='0' uci -q set firewall.cdn_cache_ssl_redirect.enabled='0' uci commit firewall /etc/init.d/firewall reload 2>/dev/null } start_service() { validate_section main start_cdn_cache } start_cdn_cache() { [ "$2" = 0 ] || { echo "Validation failed" >&2 return 1 } [ "$enabled" = "1" ] || { echo "CDN Cache is disabled" return 0 } # Check if Squid is installed if ! command -v squid >/dev/null 2>&1; then logger -t cdn-cache "ERROR: Squid is not installed" echo "Squid is not installed. Install with: opkg install squid" return 1 fi # Setup required directories mkdir -p "$cache_dir" "$LOG_DIR" /var/spool/squid chown -R squid:squid "$cache_dir" "$LOG_DIR" /var/spool/squid 2>/dev/null # Setup SSL if enabled if [ "$ssl_bump" = "1" ]; then generate_ca_cert init_ssl_db fi generate_squid_config "$cache_dir" "$cache_size" "$max_object_size" \ "$cache_valid" "$listen_port" "$transparent" "$ssl_bump" # Initialize cache if needed (blocking call, run once) if [ ! -d "$cache_dir/00" ]; then logger -t cdn-cache "Initializing Squid cache directory..." /usr/sbin/squid -f "$SQUID_CONF" -z -N 2>/dev/null fi # Setup transparent proxy if enabled setup_transparent "$listen_port" "$transparent" "$ssl_bump" # Remove any stale PID file rm -f /var/run/cdn-cache.pid procd_open_instance cdn-cache procd_set_param command /usr/sbin/squid -f "$SQUID_CONF" -sYC -N procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} procd_set_param stdout 1 procd_set_param stderr 1 procd_close_instance logger -t cdn-cache "CDN Cache (Squid) started on port $listen_port" } stop_service() { remove_transparent logger -t cdn-cache "CDN Cache proxy stopped" } reload_service() { # Send reconfigure signal to Squid if [ -f /var/run/cdn-cache.pid ]; then kill -HUP $(cat /var/run/cdn-cache.pid) 2>/dev/null logger -t cdn-cache "CDN Cache configuration reloaded" else stop start fi } service_triggers() { procd_add_reload_trigger "cdn-cache" }