diff --git a/README.md b/README.md index 21780b0de2..3d16683a1f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ If you have a spare domain name you can configure applications to be accessible * [Bazarr](https://github.com/morpheus65535/bazarr) - companion to Radarr and Sonarr for downloading subtitles * [Beets](https://beets.io/) - media library management system for obsessive music geeks * [Blaze](https://github.com/blenderskool/blaze) - File sharing progressive web app +* [Blocky](https://0xerr0r.github.io/blocky/) - Fast and lightweight DNS proxy as ad-blocker * [Booksonic](https://booksonic.org/) - The selfhosted audiobook server * [Calibre-web](https://github.com/janeczku/calibre-web) - Provides a clean interface for browsing, reading and downloading eBooks using an existing Calibre database. * [Changedetection.io](https://github.com/dgtlmoon/changedetection.io) - Free open source website change detection, monitor and notification service diff --git a/nas.yml b/nas.yml index 9bd7c65730..77ecdde792 100644 --- a/nas.yml +++ b/nas.yml @@ -94,6 +94,10 @@ tags: - blaze + - role: blocky + tags: + - blocky + - role: booksonic tags: - booksonic diff --git a/roles/blocky/defaults/main.yml b/roles/blocky/defaults/main.yml new file mode 100644 index 0000000000..20031d46f1 --- /dev/null +++ b/roles/blocky/defaults/main.yml @@ -0,0 +1,32 @@ +--- +blocky_enabled: false +blocky_available_externally: false + +# directories +blocky_data_directory: "{{ docker_home }}/blocky" + +# network +blocky_dns_tcp_port: "53" +blocky_dns_udp_port: "53" +blocky_dot_port: "853" +blocky_prometheus_stats_http_port: "4001" +blocky_prometheus_stats_https_port: "4002" +blocky_frontend_port: "8004" +blocky_hostname: "blocky" +blocky_network_name: "blocky" + +# specs +blocky_memory: 1g +blocky_frontend_memory: 1g + +# docker +blocky_container_name: blocky +blocky_image_name: "spx01/blocky" +blocky_image_version: latest +blocky_frontend_container_name: blocky-frontend +blocky_frontend_image_name: "ghcr.io/mozart409/blocky-frontend" +blocky_frontend_image_version: latest +blocky_user_id: "1000" +blocky_group_id: "1000" + +# blocky diff --git a/roles/blocky/docs/blocky.md b/roles/blocky/docs/blocky.md new file mode 100644 index 0000000000..dbbc067cbb --- /dev/null +++ b/roles/blocky/docs/blocky.md @@ -0,0 +1,11 @@ +# Blocky + +Homepage: + +Fast and lightweight DNS proxy as ad-blocker for local network with many features + +## Usage + +Set `blocky_enabled: true` in your `inventories//nas.yml` file. Edit the config file `files/config.yml` according to [Blocky configuration](https://0xerr0r.github.io/blocky/v0.22/configuration/). Run the playbook. + +Blocky web interface can be found at . diff --git a/roles/blocky/files/config.yml b/roles/blocky/files/config.yml new file mode 100644 index 0000000000..4277774e12 --- /dev/null +++ b/roles/blocky/files/config.yml @@ -0,0 +1,331 @@ +# REVIEW: manual changelog entry + +upstreams: + groups: + # these external DNS resolvers will be used. Blocky picks 2 random resolvers from the list for each query + # format for resolver: [net:]host:[port][/path]. net could be empty (default, shortcut for tcp+udp), tcp+udp, tcp, udp, tcp-tls or https (DoH). If port is empty, default port will be used (53 for udp and tcp, 853 for tcp-tls, 443 for https (Doh)) + # this configuration is mandatory, please define at least one external DNS resolver + default: + # example for tcp+udp IPv4 server (https://digitalcourage.de/) + # - 5.9.164.112 + # Cloudflare + - 1.1.1.1 + # example for DNS-over-TLS server (DoT) + - tcp-tls:fdns1.dismail.de:853 + # example for DNS-over-HTTPS (DoH) + - https://dns.digitale-gesellschaft.ch/dns-query + # optional: use client name (with wildcard support: * - sequence of any characters, [0-9] - range) + # or single ip address / client subnet as CIDR notation + laptop*: + - 123.123.123.123 + # optional: Determines what strategy blocky uses to choose the upstream servers. + # accepted: parallel_best, strict + # default: parallel_best + strategy: parallel_best + # optional: timeout to query the upstream resolver. Default: 2s + timeout: 2s + +# optional: If true, blocky will fail to start unless at least one upstream server per group is reachable. Default: false +startVerifyUpstream: true + +# optional: Determines how blocky will create outgoing connections. This impacts both upstreams, and lists. +# accepted: dual, v4, v6 +# default: dual +connectIPVersion: dual + +# optional: custom IP address(es) for domain name (with all sub-domains). Multiple addresses must be separated by a comma +# example: query "printer.lan" or "my.printer.lan" will return 192.168.178.3 +# customDNS: +# customTTL: 1h +# # optional: if true (default), return empty result for unmapped query types (for example TXT, MX or AAAA if only IPv4 address is defined). +# # if false, queries with unmapped types will be forwarded to the upstream resolver +# filterUnmappedTypes: true +# # optional: replace domain in the query with other domain before resolver lookup in the mapping +# rewrite: +# example.com: printer.lan +# mapping: +# printer.lan: 192.168.178.3,2001:0db8:85a3:08d3:1319:8a2e:0370:7344 + +# optional: definition, which DNS resolver(s) should be used for queries to the domain (with all sub-domains). Multiple resolvers must be separated by a comma +# Example: Query client.fritz.box will ask DNS server 192.168.178.1. This is necessary for local network, to resolve clients by host name +# conditional: +# # optional: if false (default), return empty result if after rewrite, the mapped resolver returned an empty answer. If true, the original query will be sent to the upstream resolver +# # Example: The query "blog.example.com" will be rewritten to "blog.fritz.box" and also redirected to the resolver at 192.168.178.1. If not found and if `fallbackUpstream` was set to `true`, the original query "blog.example.com" will be sent upstream. +# # Usage: One usecase when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain. +# fallbackUpstream: false +# # optional: replace domain in the query with other domain before resolver lookup in the mapping +# rewrite: +# example.com: fritz.box +# mapping: +# fritz.box: 192.168.178.1 +# lan.net: 192.168.178.1,192.168.178.2 + +# optional: use black and white lists to block queries (for example ads, trackers, adult pages etc.) +blocking: + # definition of blacklist groups. Can be external link (http/https) or local file + blackLists: + ads: + - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt + - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts + - http://sysctl.org/cameleon/hosts + - https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt + - | + # inline definition with YAML literal block scalar style + # hosts format + someadsdomain.com + special: + - https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts + # definition of whitelist groups. Attention: if the same group has black and whitelists, whitelists will be used to disable particular blacklist entries. If a group has only whitelist entries -> this means only domains from this list are allowed, all other domains will be blocked + whiteLists: + ads: + # - whitelist.txt + - | + # inline definition with YAML literal block scalar style + # hosts format + whitelistdomain.com + # this is a regex + /^banners?[_.-]/ + # definition: which groups should be applied for which client + clientGroupsBlock: + # default will be used, if no special definition for a client name exists + default: + - ads + - special + # use client name (with wildcard support: * - sequence of any characters, [0-9] - range) + # or single ip address / client subnet as CIDR notation + laptop*: + - ads + 192.168.178.1/24: + - special + # which response will be sent, if query is blocked: + # zeroIp: 0.0.0.0 will be returned (default) + # nxDomain: return NXDOMAIN as return code + # comma separated list of destination IP addresses (for example: 192.100.100.15, 2001:0db8:85a3:08d3:1319:8a2e:0370:7344). Should contain ipv4 and ipv6 to cover all query types. Useful with running web server on this address to display the "blocked" page. + blockType: zeroIp + # optional: TTL for answers to blocked domains + # default: 6h + blockTTL: 1m + # optional: Configure how lists, AKA sources, are loaded + loading: + # optional: list refresh period in duration format. + # Set to a value <= 0 to disable. + # default: 4h + refreshPeriod: 24h + # optional: Applies only to lists that are downloaded (HTTP URLs). + downloads: + # optional: timeout for list download (each url). Use large values for big lists or slow internet connections + # default: 5s + timeout: 60s + # optional: Maximum download attempts + # default: 3 + attempts: 5 + # optional: Time between the download attempts + # default: 500ms + cooldown: 10s + # optional: Maximum number of lists to process in parallel. + # default: 4 + concurrency: 16 + # optional: if failOnError, application startup will fail if at least one list can't be downloaded/opened + # default: blocking + strategy: failOnError + # Number of errors allowed in a list before it is considered invalid. + # A value of -1 disables the limit. + # default: 5 + maxErrorsPerSource: 5 + +# optional: configuration for caching of DNS responses +caching: + # duration how long a response must be cached (min value). + # If <=0, use response's TTL, if >0 use this value, if TTL is smaller + # Default: 0 + minTime: 5m + # duration how long a response must be cached (max value). + # If <0, do not cache responses + # If 0, use TTL + # If > 0, use this value, if TTL is greater + # Default: 0 + maxTime: 30m + # Max number of cache entries (responses) to be kept in cache (soft limit). Useful on systems with limited amount of RAM. + # Default (0): unlimited + maxItemsCount: 0 + # if true, will preload DNS results for often used queries (default: names queried more than 5 times in a 2-hour time window) + # this improves the response time for often used queries, but significantly increases external traffic + # default: false + prefetching: true + # prefetch track time window (in duration format) + # default: 120 + prefetchExpires: 2h + # name queries threshold for prefetch + # default: 5 + prefetchThreshold: 5 + # Max number of domains to be kept in cache for prefetching (soft limit). Useful on systems with limited amount of RAM. + # Default (0): unlimited + prefetchMaxItemsCount: 0 + # Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results. + # Default: 30m + cacheTimeNegative: 30m + +# optional: configuration of client name resolution +clientLookup: + # optional: this DNS resolver will be used to perform reverse DNS lookup (typically local router) + upstream: 192.168.2.1 + # optional: some routers return multiple names for client (host name and user defined name). Define which single name should be used. + # Example: take second name if present, if not take first name + singleNameOrder: + - 2 + - 1 + # optional: custom mapping of client name to IP addresses. Useful if reverse DNS does not work properly or just to have custom client names. + clients: + laptop: + - 192.168.178.29 + +# optional: configuration for prometheus metrics endpoint +prometheus: + # enabled if true + enable: true + # url path, optional (default '/metrics') + path: /metrics + +# optional: write query information (question, answer, client, duration etc.) to daily csv file +# queryLog: +# # optional one of: mysql, postgresql, csv, csv-client. If empty, log to console +# type: mysql +# # directory (should be mounted as volume in docker) for csv, db connection string for mysql/postgresql +# target: db_user:db_password@tcp(db_host_or_ip:3306)/db_name?charset=utf8mb4&parseTime=True&loc=Local +# #postgresql target: postgres://user:password@db_host_or_ip:5432/db_name +# # if > 0, deletes log files which are older than ... days +# logRetentionDays: 7 +# # optional: Max attempts to create specific query log writer, default: 3 +# creationAttempts: 1 +# # optional: Time between the creation attempts, default: 2s +# creationCooldown: 2s +# # optional: Which fields should be logged. You can choose one or more from: clientIP, clientName, responseReason, responseAnswer, question, duration. If not defined, it logs all fields +# fields: +# - clientIP +# - duration + +# optional: Blocky can synchronize its cache and blocking state between multiple instances through redis. +# redis: +# # Server address and port or master name if sentinel is used +# address: redismaster +# # Username if necessary +# username: usrname +# # Password if necessary +# password: passwd +# # Database, default: 0 +# database: 2 +# # Connection is required for blocky to start. Default: false +# required: true +# # Max connection attempts, default: 3 +# connectionAttempts: 10 +# # Time between the connection attempts, default: 1s +# connectionCooldown: 3s +# # Sentinal username if necessary +# sentinelUsername: usrname +# # Sentinal password if necessary +# sentinelPassword: passwd +# # List with address and port of sentinel hosts(sentinel is activated if at least one sentinel address is configured) +# sentinelAddresses: +# - redis-sentinel1:26379 +# - redis-sentinel2:26379 +# - redis-sentinel3:26379 + +# optional: Mininal TLS version that the DoH and DoT server will use +minTlsServeVersion: 1.3 + +# if https port > 0: path to cert and key file for SSL encryption. if not set, self-signed certificate will be generated +#certFile: server.crt +#keyFile: server.key + +# optional: use these DNS servers to resolve blacklist urls and upstream DNS servers. It is useful if no system DNS resolver is configured, and/or to encrypt the bootstrap queries. +bootstrapDns: + - tcp+udp:1.1.1.1 + - https://1.1.1.1/dns-query + - upstream: https://dns.digitale-gesellschaft.ch/dns-query + ips: + - 185.95.218.42 + +# optional: drop all queries with following query types. Default: empty +filtering: + queryTypes: + - AAAA + +# optional: return NXDOMAIN for queries that are not FQDNs. +fqdnOnly: + # default: false + enable: true + +# optional: if path defined, use this file for query resolution (A, AAAA and rDNS). Default: empty +# hostsFile: +# # optional: Hosts files to parse +# sources: +# - /etc/hosts +# - https://example.com/hosts +# - | +# # inline hosts +# 127.0.0.1 example.com +# # optional: TTL, default: 1h +# hostsTTL: 30m +# # optional: Whether loopback hosts addresses (127.0.0.0/8 and ::1) should be filtered or not +# # default: false +# filterLoopback: true +# # optional: Configure how sources are loaded +# loading: +# # optional: file refresh period in duration format. +# # Set to a value <= 0 to disable. +# # default: 4h +# refreshPeriod: 24h +# # optional: Applies only to files that are downloaded (HTTP URLs). +# downloads: +# # optional: timeout for file download (each url). Use large values for big files or slow internet connections +# # default: 5s +# timeout: 60s +# # optional: Maximum download attempts +# # default: 3 +# attempts: 5 +# # optional: Time between the download attempts +# # default: 500ms +# cooldown: 10s +# # optional: Maximum number of files to process in parallel. +# # default: 4 +# concurrency: 16 +# # optional: if failOnError, application startup will fail if at least one file can't be downloaded/opened +# # default: blocking +# strategy: failOnError +# # Number of errors allowed in a file before it is considered invalid. +# # A value of -1 disables the limit. +# # default: 5 +# maxErrorsPerSource: 5 + +# optional: ports configuration +ports: + # optional: DNS listener port(s) and bind ip address(es), default 53 (UDP and TCP). Example: 53, :53, "127.0.0.1:5353,[::1]:5353" + dns: 53 + # optional: Port(s) and bind ip address(es) for DoT (DNS-over-TLS) listener. Example: 853, 127.0.0.1:853 + tls: 853 + # optional: Port(s) and optional bind ip address(es) to serve HTTPS used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:443. Example: 443, :443, 127.0.0.1:443,[::1]:443 + https: 443 + # optional: Port(s) and optional bind ip address(es) to serve HTTP used for prometheus metrics, pprof, REST API, DoH... If you wish to specify a specific IP, you can do so such as 192.168.0.1:4000. Example: 4000, :4000, 127.0.0.1:4000,[::1]:4000 + http: 4000 + +# optional: logging configuration +log: + # optional: Log level (one from debug, info, warn, error). Default: info + level: info + # optional: Log format (text or json). Default: text + format: text + # optional: log timestamps. Default: true + timestamp: true + # optional: obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. Default: false + privacy: false + +# optional: add EDE error codes to dns response +ede: + # enabled if true, Default: false + enable: true + +# optional: configure optional Special Use Domain Names (SUDN) +specialUseDomains: + # optional: block recomended private TLDs + # default: true + rfc6762-appendixG: true \ No newline at end of file diff --git a/roles/blocky/molecule/default/molecule.yml b/roles/blocky/molecule/default/molecule.yml new file mode 100644 index 0000000000..eafcdd9d14 --- /dev/null +++ b/roles/blocky/molecule/default/molecule.yml @@ -0,0 +1,17 @@ +--- +provisioner: + inventory: + group_vars: + all: + blocky_enabled: true + blocky_data_directory: "/tmp" +platforms: + - name: instance + image: geerlingguy/docker-ubuntu2204-ansible:latest + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + - /var/run/docker.sock:/var/run/docker.sock + - /tmp:/tmp + cgroupns_mode: host + privileged: true + pre_build_image: true \ No newline at end of file diff --git a/roles/blocky/molecule/default/side_effect.yml b/roles/blocky/molecule/default/side_effect.yml new file mode 100644 index 0000000000..8be77df675 --- /dev/null +++ b/roles/blocky/molecule/default/side_effect.yml @@ -0,0 +1,10 @@ +--- +- name: Stop + hosts: all + become: true + tasks: + - name: "Include {{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }} role" + ansible.builtin.include_role: + name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" + vars: + blocky_enabled: false diff --git a/roles/blocky/molecule/default/verify.yml b/roles/blocky/molecule/default/verify.yml new file mode 100644 index 0000000000..b78a995d63 --- /dev/null +++ b/roles/blocky/molecule/default/verify.yml @@ -0,0 +1,26 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - name: Include vars + ansible.builtin.include_vars: + file: ../../defaults/main.yml + + - name: Get blocky container state + community.docker.docker_container: + name: "{{ blocky_container_name }}" + register: result + + - name: Get blocky frontend container state + community.docker.docker_container: + name: "{{ blocky_frontend_container_name }}" + register: result_frontend + + - name: Check if blocky containers are running + ansible.builtin.assert: + that: + - result.container['State']['Status'] == "running" + - result.container['State']['Restarting'] == false + - result_frontend.container['State']['Status'] == "running" + - result_frontend.container['State']['Restarting'] == false diff --git a/roles/blocky/molecule/default/verify_stopped.yml b/roles/blocky/molecule/default/verify_stopped.yml new file mode 100644 index 0000000000..426ae0b35d --- /dev/null +++ b/roles/blocky/molecule/default/verify_stopped.yml @@ -0,0 +1,26 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - name: Include vars + ansible.builtin.include_vars: + file: ../../defaults/main.yml + + - name: Try and stop and remove blocky frontend + community.docker.docker_container: + name: "{{ blocky_frontend_container_name }}" + state: absent + register: result_frontend + + - name: Try and stop and remove blocky + community.docker.docker_container: + name: "{{ blocky_container_name }}" + state: absent + register: result + + - name: Check if blocky is stopped + ansible.builtin.assert: + that: + - not result_frontend.changed + - not result.changed diff --git a/roles/blocky/tasks/main.yml b/roles/blocky/tasks/main.yml new file mode 100644 index 0000000000..90ceb5aec3 --- /dev/null +++ b/roles/blocky/tasks/main.yml @@ -0,0 +1,89 @@ +--- +- name: Start Blocky + block: + - name: Create Blocky Directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + with_items: + - "{{ blocky_data_directory }}/config" + - "{{ blocky_data_directory }}/logs" + - "{{ blocky_data_directory }}/blacklists" + - "{{ blocky_data_directory }}/whitelists" + + - name: Create Blocky Network + community.docker.docker_network: + name: "{{ blocky_network_name }}" + + - name: Copy config file + ansible.builtin.copy: + src: config.yml + dest: "{{ blocky_data_directory }}/config/config.yml" + + - name: Create Blocky Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ blocky_container_name }}" + image: "{{ blocky_image_name }}:{{ blocky_image_version }}" + pull: true + volumes: + - "/etc/localtime:/etc/localtime:ro" + - "{{ blocky_data_directory }}/config/config.yml:/app/config.yml" + - "{{ blocky_data_directory }}/logs:/logs" + - "{{ blocky_data_directory }}/blacklists:/app/blacklists" + - "{{ blocky_data_directory }}/whitelists:/app/whitelists" + ports: + - "{{ blocky_dns_tcp_port }}:53/tcp" + - "{{ blocky_dns_udp_port }}:53/udp" + - "{{ blocky_prometheus_stats_http_port }}:4000/tcp" + - "{{ blocky_prometheus_stats_https_port }}:4000/tcp" + env: + TZ: "{{ ansible_nas_timezone }}" + PUID: "{{ blocky_user_id }}" + PGID: "{{ blocky_group_id }}" + restart_policy: unless-stopped + memory: "{{ blocky_memory }}" + networks: + - name: "{{ blocky_network_name }}" + network_mode: "{{ blocky_network_name }}" + labels: + traefik.enable: "false" + + - name: Create Blocky Frontend Docker Container + community.docker.docker_container: + container_default_behavior: no_defaults + name: "{{ blocky_frontend_container_name }}" + image: "{{ blocky_frontend_image_name }}:{{ blocky_frontend_image_version }}" + pull: true + ports: + - "{{ blocky_frontend_port }}:8002" + env: + TZ: "{{ ansible_nas_timezone }}" + PUID: "{{ blocky_user_id }}" + PGID: "{{ blocky_group_id }}" + API_URL: "http://{{ blocky_container_name }}:4000" + restart_policy: unless-stopped + memory: "{{ blocky_frontend_memory }}" + networks: + - name: "{{ blocky_network_name }}" + network_mode: "{{ blocky_network_name }}" + labels: + traefik.enable: "{{ blocky_available_externally | string }}" + traefik.http.routers.blocky.rule: "Host(`{{ blocky_hostname }}.{{ ansible_nas_domain }}`)" + traefik.http.routers.blocky.tls.certresolver: "letsencrypt" + traefik.http.routers.blocky.tls.domains[0].main: "{{ ansible_nas_domain }}" + traefik.http.routers.blocky.tls.domains[0].sans: "*.{{ ansible_nas_domain }}" + traefik.http.services.blocky.loadbalancer.server.port: "8002" + when: blocky_enabled is true + +- name: Stop Blocky + block: + - name: Stop Blocky Frontend + community.docker.docker_container: + name: "{{ blocky_frontend_container_name }}" + state: absent + - name: Stop Blocky + community.docker.docker_container: + name: "{{ blocky_container_name }}" + state: absent + when: blocky_enabled is false diff --git a/website/docs/applications/system-tools/blocky.md b/website/docs/applications/system-tools/blocky.md new file mode 100644 index 0000000000..e5691ac706 --- /dev/null +++ b/website/docs/applications/system-tools/blocky.md @@ -0,0 +1,12 @@ +--- +title: "Blocky" +description: "Fast and lightweight DNS proxy as ad-blocker" +--- + +Fast and lightweight DNS proxy as ad-blocker for local network with many features + +## Usage + +Set `blocky_enabled: true` in your `inventories//nas.yml` file. Edit the config file `files/config.yml` according to [Blocky configuration](https://0xerr0r.github.io/blocky/v0.22/configuration/). Run the playbook. + +Blocky web interface can be found at .