# Downloader Plugin + Cron Scheduling
#
# This configuration demonstrates a lightweight, plugin-based approach
# for automatically downloading and updating DNS rule files.
#
# Architecture:
#   1. CronPlugin triggers on schedule (every 6 hours: 00:00, 06:00, 12:00, 18:00 local time)
#   2. CronPlugin invokes the Downloader plugin via invoke_plugin action
#   3. Downloader downloads files and updates them atomically
#   4. DomainSet/IPSet/Hosts automatically detect changes and reload
#
# Logging:
#   - CronPlugin outputs DEBUG logs for job scheduling and execution
#   - DownloaderPlugin outputs detailed logs for each file download
#   - Set log level to debug or trace to see detailed operation logs
#

# ============================================================================
# ADMIN API CONFIGURATION
# ============================================================================

admin:
  # Enable admin API for runtime management
  # Admin API provides endpoints for:
  #   - /api/cache/control - Cache management (clear, stats)
  #   - /api/config/reload - Config hot-reload
  #   - /api/server/status - Server status
  enabled: true
  # Listen address (default: 127.0.0.1:8080)
  # Warning: Do NOT expose to untrusted networks!
  # Use a reverse proxy with authentication in production
  addr: "127.0.0.1:8080"

metrics:
  # Enable monitoring server for Prometheus metrics and health checks
  enabled: true
  # Listen address (default: 127.0.0.1:9090)
  addr: "127.0.0.1:9090"

# ============================================================================
# PLUGINS CONFIGURATION
# ============================================================================

plugins:
  # ========================================================================
  # Data Providers - Load DNS rule files
  # ========================================================================

  # Load domain blocklists (Ad, etc.)
  - tag: domain_reject_list
    type: domain_set
    args:
      files:
        - "reject-list.txt"
      auto_reload: true # Automatically reload when file changes

  # Load domain proxy list
  - tag: domain_proxy_list
    type: domain_set
    args:
      files:
        - "proxy-list.txt"
        - "proxy-ext-list.txt"
      auto_reload: true

  # Load domain direct list
  - tag: domain_direct_list
    type: domain_set
    args:
      files:
        - "direct-list.txt"
        - "apple-cn.txt"
        - "my-domain-list.txt"
      auto_reload: true

  # Load IP direct list
  - tag: ip_direct_list
    type: ip_set
    args:
      files:
        - "china-ip-list.txt"
        - "white-ip-list.txt"
      auto_reload: true

  # Load hosts files
  - tag: hosts_files
    type: hosts
    args:
      files:
        - "hosts.txt"
        - "hosts-github.txt"
      auto_reload: true

  # ========================================================================
  # Cron Scheduler - Triggers downloader on schedule
  # ========================================================================

  - tag: auto_update_scheduler
    type: cron
    args:
      jobs:
        - name: auto_update
          # Cron expression: minute hour day month weekday
          # Note: cronexpr uses the standard crontab order (minute hour day month weekday)
          # Examples:
          #   "5 2 * * *" = every day at 02:05 (local time)
          #   "0 */6 * * *" = every 6 hours at minute 0 (00:00, 06:00, 12:00, 18:00 local time)
          #   "0 * * * *" = every hour at minute 0
          # You may optionally append a TZ name as the last token: e.g. "0 */6 * * * UTC"
          cron: "32 11  * * *"
          action:
            invoke_plugin:
              type: "downloader"
              args:
                files:
                  - url: "https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/gfw.txt"
                    path: "gfw.txt"
                  - url: "https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/reject-list.txt"
                    path: "reject-list.txt"
                  - url: "https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/direct-list.txt"
                    path: "direct-list.txt"
                  - url: "https://raw.githubusercontent.com/Loyalsoldier/geoip/release/text/cn.txt"
                    path: "china-ip-list.txt"
                  - url: "https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/proxy-list.txt"
                    path: "proxy-list.txt"
                  - url: "https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/apple-cn.txt"
                    path: "apple-cn.txt"
                  - url: "https://raw.githubusercontent.com/lazywalker/ghosts/refs/heads/master/hosts.mdns"
                    path: "hosts-github.txt"
                timeout_secs: 30
                concurrent: false

  # ========================================================================
  # Cache, Redirect Plugins, and ROS Addrlist Updater
  # ========================================================================

  # ========================================================================
  #   * Cache with LazyCache Optimization
  # ========================================================================

  # Main cache plugin with lazycache enabled for hot entries
  # LazyCache automatically refreshes frequently accessed entries before
  # they expire, preventing cache misses and reducing query latency.
  #
  # How LazyCache works:
  #   1. When a cached entry is accessed (cache hit)
  #   2. If remaining TTL < lazycache_threshold (e.g., 5% of original TTL)
  #   3. The entry is marked for refresh in a separate query
  #   4. This keeps hot entries fresh without waiting for expiry
  #
  # Benefits:
  #   - Eliminates cache miss latency for hot domains
  #   - Reduces upstream DNS load by proactive refresh
  #   - Improves user experience with consistent response times
  - tag: cache
    type: cache
    args:
      # Maximum number of cached entries (default: 1024)
      size: 1024

      # Optional: Enable caching of error responses (NXDOMAIN, SERVFAIL, etc.)
      negative_cache: true
      # TTL for cached error responses (in seconds)
      negative_ttl: 300

      # Optional: Enable LazyCache optimization
      enable_lazycache: true
      # Refresh hot entries when TTL drops to 5% of original value
      # Example: entry with TTL=300, refresh when remaining < 15 seconds
      lazycache_threshold: 0.05

      # Optional: Enable stale-serving with extended cache TTL (in seconds)
      # Set `enable_lazycache` to false to test cache_ttl stale-serving
      # When DNS TTL expires but cache TTL remains, serve stale response with small TTL
      # and refresh in background. Set to desired cache duration (e.g., 600 for 10 minutes)
      cache_ttl: 600

      # TODO: Prefetch extension can be enabled here in future
      # Optional: Enable prefetch for additional performance
      # Prefetch is more aggressive than lazycache and refreshes at higher threshold
      # enable_prefetch: true
      # prefetch_threshold: 0.1  # Refresh at 10% remaining TTL

  # ========================================================================
  # Alternative Cache Configurations (Commented Out Examples)
  # ========================================================================

  # Example 1: Conservative cache (large size, high threshold)
  # Suitable for stable DNS environments with many unique domains
  # - tag: cache_conservative
  #   type: cache
  #   args:
  #     size: 5000  # Larger cache for more domains
  #     lazycache_threshold: 0.1  # Refresh at 10% TTL (more conservative)

  # Example 2: Aggressive cache (small size, low threshold)
  # Suitable for high-traffic environments with limited memory
  # - tag: cache_aggressive
  #   type: cache
  #   args:
  #     size: 512  # Smaller cache to save memory
  #     lazycache_threshold: 0.02  # Refresh at 2% TTL (more aggressive)

  # Example 3: Performance cache (without error caching)
  # Useful when you want to requery failed domains instead of caching errors
  # - tag: cache_positive_only
  #   type: cache
  #   args:
  #     size: 1024
  #     negative_cache: false  # Don't cache errors
  #     enable_lazycache: true
  #     lazycache_threshold: 0.05

  # redirect plugin for specific domains
  - tag: redirect
    type: redirect
    args:
      rules:
        - www.cnbeta.com www.cnbeta.com.cdn.cloudflare.net

  # ROS Addrlist updater for gfwlist IPs
  - tag: add_gfwlist
    type: ros_addrlist
    args:
      addrlist: "lazydns-gfwlist"
      server: "http://192.168.88.1:80"
      user: "lazydns"
      passwd: "Lazyisgood"
      mask4: 24
      mask6: 32

  # ========================================================================
  # Upstream Servers
  # ========================================================================

  # forward to domestic DNS, concurrent queries
  - tag: upstream_direct
    type: forward
    args:
      concurrent: 2
      upstreams:
        - addr: udp://119.29.29.29 # Tencent Public DNS
        - addr: udp://223.5.5.5 # AliDNS Public DNS
      health_checks: true
      max_attempts: 3

  # forward to proxy DNS, concurrent queries
  - tag: upstream_proxy
    type: forward
    args:
      concurrent: 2
      upstreams:
        - addr: tcp://8.8.8.8 # Google Public DNS
        - addr: tcp://1.1.1.1 # Cloudflare DNS
      health_checks: true
      max_attempts: 3

  # blackhole upstream for rejected domains
  - tag: upstream_reject
    type: blackhole

  # ========================================================================
  # Sub sequence and Fallback Logic
  # ========================================================================

  # direct sub-sequence, accpt domestic IPs, drop others
  - tag: sequence_direct
    type: sequence
    args:
      - exec: $upstream_direct
      - matches: resp_ip $ip_direct_list
        exec: drop_resp
      - exec: accept

  # proxy sub-sequence
  - tag: sequence_proxy
    type: sequence
    args:
      - exec: $upstream_proxy
      - exec: accept

  # sub-sequence for gfwlist,
  # adding IPs to some list(by exec ROS updater) before accepting when not domestic
  - tag: sequence_gfwlist
    type: sequence
    args:
      - exec: ttl 300-3600
      - matches: "!resp_ip $ip_direct_list"
        exec: $add_gfwlist
      - exec: accept

  # fallback sequence
  - tag: fallback
    type: fallback
    args:
      primary: sequence_direct
      secondary: sequence_proxy
      threshold: 500
      always_standby: true

  # ========================================================================
  # Main sequence - the core DNS processing logic
  # ========================================================================

  # main sequence
  - tag: sequence_main
    type: sequence
    args:
      # check hosts files first
      - exec: $hosts_files
      - matches: has_resp
        exec: accept

      # reject PTR and DNSSEC to fix some iOS issues
      - matches: qtype 12 65
        exec: reject 0

      # block ads
      - matches: qname $domain_reject_list
        # demonstration: use inline quick-setup to blackhole
        exec: black_hole 127.0.0.1
        # or use the upstream_reject defined above
        # exec: $upstream_reject

      - exec: prefer_ipv4

      - exec: $redirect

      # skip cache for dynamic domains, others use cache
      - matches: "!qname 00006801.com"
        exec: $cache
      - matches: has_resp
        exec: accept

      # domestic domains
      - matches: qname $domain_direct_list
        exec: $upstream_direct
      - matches: has_resp
        exec: accept

      # foreign domains
      - matches: qname $domain_proxy_list
        exec: $upstream_proxy
      - matches: has_resp
        exec: jump sequence_gfwlist

      # other unknown domains
      - exec: $fallback

  # ========================================================================
  # Server Configurations, listen on multiple protocols and ports
  # ========================================================================

  # UDP server (built-in)
  - tag: udp_server
    type: udp_server
    args:
      entry: sequence_main
      listen: :5354

  # TCP server (built-in)
  - tag: tcp_server
    type: tcp_server
    args:
      entry: sequence_main
      listen: :5354

  # DNS over HTTPS (DoH) server (requires tls and doh features)
  - tag: doh_server
    type: doh_server
    args:
      entry: sequence_main
      # use unprivileged port for examples; change to :443 for production
      listen: :8443
      # Paths to certificate and private key PEM files (relative to this config dir)
      cert_file: certs/cert.pem
      key_file: certs/key.pem

  # DNS over TLS (DoT) server (requires tls and dot features)
  - tag: dot_server
    type: dot_server
    args:
      entry: sequence_main
      # standard DoT port is 853; use 8853 in examples if you need non-root port
      listen: :8853
      # Paths relative to this config directory
      cert_file: certs/cert.pem
      key_file: certs/key.pem

  # DNS over QUIC (DoQ) server (requires tls and doq features)
  - tag: doq_server
    type: doq_server
    args:
      entry: sequence_main
      # standard DoQ port is 784; use 8784 in examples if you need non-root port
      listen: :8784
      # Paths relative to this config directory
      cert_file: certs/cert.pem
      key_file: certs/key.pem

# ============================================================================
# LOGGING CONFIGURATION
# ============================================================================

log:
  # * trace | debug | info |warn | error
  level: debug

  # * text (by default) | json
  # format: text

  # * optional; if not set, logs to stdout
  # file: test.log

  # * daily | hourly | never (by default)
  # rotate: daily

  # * optional; overrides parent dir of `file`
  # rotate_dir: /var/log/lazydns
# ============================================================================
# END OF CONFIGURATION
# ============================================================================

# ============================================================================
# FILE UPDATER SCHEDULE DETAILS
# ============================================================================
#
# The scheduler will trigger at:
#   - Time: 02:05 UTC every day
#   - Action: Download all files in the files list
#   - Download Mode: Sequential (one at a time) to avoid bandwidth spikes
#   - Timeout: 30 seconds per file
#   - Retry: Built-in retry on network errors
#
# After files are downloaded:
#   1. Files are written to temporary locations
#   2. Atomically renamed to final paths (no partial files)
#   3. File watches detect the change automatically
#   4. DomainSet/IPSet/Hosts reload the new content
#   5. DNS queries immediately use updated rules
#
# Performance Notes:
#   - With 6 files and sequential mode: ~6-12 seconds typical (varies by file size)
#   - Concurrent mode: ~2-3 seconds but higher bandwidth usage
#   - File reloads happen in background, no DNS query interruption
#   - Failed downloads logged but don't prevent DNS operation
#
# For production deployments:
#   - Monitor logs for download failures: grep "download failed" logs/lazydns.log
#   - Verify file sizes after update: ls -la *.txt
#   - Consider downloading during off-peak hours (e.g., 03:05 UTC instead of 02:05)
#   - Set timeout_secs based on file sizes and network speed
