about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2025-09-07 17:32:13 +0200
committerRobert Günzler <r@gnzler.io>2025-09-25 12:56:47 +0200
commit0bb17cd2f34918add1ddaedeb0a66531dea561a3 (patch)
treef1d3a825273c8a2ab3e804876328bd3f1a939f68
parent332d333fa24c8b1943799aadb017f61e642008e1 (diff)
go-away: use config.yml and vendor all configuration files
Signed-off-by: Robert Günzler <r@gnzler.io>
Diffstat (limited to '')
-rw-r--r--bundles/go-away/files/config/config.yml90
-rw-r--r--bundles/go-away/files/config/policy.yml183
-rw-r--r--bundles/go-away/files/config/snippets/bot-betterstack.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-bingbot.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-duckduckbot.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-googlebot.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-kagibot.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-qwantbot.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-uptimerobot.yml8
-rw-r--r--bundles/go-away/files/config/snippets/bot-yandexbot.yml24
-rw-r--r--bundles/go-away/files/config/snippets/challenge-dnsbl.yml6
-rw-r--r--bundles/go-away/files/config/snippets/challenge-js-pow-sha256.yml15
-rw-r--r--bundles/go-away/files/config/snippets/challenge-js-refresh.yml6
-rw-r--r--bundles/go-away/files/config/snippets/challenges-non-js.yml28
-rw-r--r--bundles/go-away/files/config/snippets/conditions-generic.yml56
-rw-r--r--bundles/go-away/files/config/snippets/networks-other.yml37
-rw-r--r--bundles/go-away/files/config/snippets/networks-private.yml22
-rwxr-xr-xbundles/go-away/files/go-away.run18
-rw-r--r--bundles/go-away/files/traefik.yaml12
-rw-r--r--bundles/go-away/items.py54
-rw-r--r--bundles/go-away/metadata.py3
-rw-r--r--nodes.py5
22 files changed, 583 insertions, 32 deletions
diff --git a/bundles/go-away/files/config/config.yml b/bundles/go-away/files/config/config.yml
new file mode 100644
index 0000000..df731a7
--- /dev/null
+++ b/bundles/go-away/files/config/config.yml
@@ -0,0 +1,90 @@
+bind:
+  # XXX: coordinate with traefik
+  address: "0.0.0.0:${node.metadata.get('go-away/port')}"
+  network: "tcp"
+
+  # Enable PROXY mode on this listener, to allow passing origin info. Default false
+  proxy: true
+
+  # Enable passthrough mode, which will allow traffic onto the backends while rules load. Default false
+  #passthrough: true
+
+# Bind the Go debug port
+#bind-debug: ":6060"
+
+# Bind the Prometheus metrics onto /metrics path on this port
+bind-metrics: "0.0.0.0:${node.metadata.get('go-away-metrics/port')}"
+
+
+# Advanced backend configuration
+# Backends setup via cmdline will be added here
+backends:
+  "gzr.im":
+    url: "http://${node.metadata.get('container_gateway', '10.89.0.1')}:${node.metadata.get('www/gzr-im/port')}"
+    http2-enabled: true
+  "gnzler.de":
+    url: "http://${node.metadata.get('container_gateway', '10.89.0.1')}:${node.metadata.get('www/gzr-im/port')}"
+    http2-enabled: true
+  "gnzler.io":
+    url: "http://${node.metadata.get('container_gateway', '10.89.0.1')}:${node.metadata.get('www/gzr-im/port')}"
+    http2-enabled: true
+  "gonca.me":
+    url: "http://${node.metadata.get('container_gateway', '10.89.0.1')}:${node.metadata.get('www/gonca-me/port')}"
+    http2-enabled: true
+
+
+# These links will be shown on the presented challenge or error pages
+links:
+  #- name: Privacy
+  #  url: "/privacy.html"
+  #- name: Contact
+  #  url: "mailto:admin@example.com"
+  #- name: Donations
+  #  url: "https://donations.example.com/abcd"
+
+# HTML Template to use for challenge or error pages
+# External templates can be included by providing a disk path
+# Bundled templates:
+#  anubis: An Anubis-like template with no configuration parameters
+#  forgejo: Looks like native Forgejo. Includes logos and resources from your instance. Supports Theme.
+#
+challenge-template: "anubis"
+
+# Allows overriding specific settings set on templates. Key-Values will be passed to templates as-is
+challenge-template-overrides:
+  # Set template theme if supported
+  #Theme: "forgejo-auto"
+
+# List of strings you can replace to alter the presentation on challenge/error templates
+# Can use other languages.
+# Note raw HTML is allowed, be careful with it.
+# Default strings exist in code, uncomment any to set it
+strings:
+  #title_challenge: "Checking you are not a bot"
+  #title_error: "Oh no!"
+  #noscript_warning: "<p>Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed the social contract around how website hosting works.</p>"
+  #details_title: "Why am I seeing this?"
+  #details_text: >
+  #  <p>
+  #  You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a>
+  #  to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>.
+  #  </p>
+  #  <p>
+  #  Mass scraping can and does cause downtime for the websites, which makes their resources inaccessible for everyone.
+  #  </p>
+  #  <p>
+  #  Please note that some challenges requires the use of modern JavaScript features and some plugins may disable these.
+  #  Disable such plugins for this domain (for example, JShelter) if you encounter any issues.
+  #  </p>
+
+  #details_contact_admin_with_request_id: "If you have any issues contact the site administrator and provide the following Request Id"
+
+  #button_refresh_page: "Refresh page"
+
+  #status_loading_challenge: "Loading challenge"
+  #status_starting_challenge: "Starting challenge"
+  #status_loading: "Loading..."
+  #status_calculating: "Calculating..."
+  #status_challenge_success: "Challenge success!"
+  #status_challenge_done_took: "Done! Took"
+  #status_error: "Error:"
diff --git a/bundles/go-away/files/config/policy.yml b/bundles/go-away/files/config/policy.yml
new file mode 100644
index 0000000..a403c38
--- /dev/null
+++ b/bundles/go-away/files/config/policy.yml
@@ -0,0 +1,183 @@
+# Define networks to be used later below
+networks:
+  # Networks will get included from snippets
+
+
+challenges:
+  # Challenges will get included from snippets
+
+conditions:
+  # Conditions will get replaced on rules AST when found as ($condition-name)
+
+  # Conditions will get included from snippets
+
+
+  is-static-asset:
+    - 'path == "/apple-touch-icon.png"'
+    - 'path == "/apple-touch-icon-precomposed.png"'
+    - 'path.matches("\\.(manifest|ttf|woff|woff2|jpg|jpeg|gif|png|webp|avif|svg|mp4|webm|css|js|mjs|wasm)$")'
+
+  is-suspicious-crawler:
+    - 'userAgent.contains("Presto/") || userAgent.contains("Trident/")'
+    # Old IE browsers
+    - 'userAgent.matches("MSIE ([2-9]|10|11)\\.")'
+    # Old Linux browsers
+    - 'userAgent.matches("Linux i[63]86") || userAgent.matches("FreeBSD i[63]86")'
+    # Old Windows browsers
+    - 'userAgent.matches("Windows (3|95|98|CE)") || userAgent.matches("Windows NT [1-5]\\.")'
+    # Old mobile browsers
+    - 'userAgent.matches("Android [1-5]\\.") || userAgent.matches("(iPad|iPhone) OS [1-9]_")'
+    # Old generic browsers
+    - 'userAgent.startsWith("Opera/")'
+    #- 'userAgent.matches("Gecko/(201[0-9]|200[0-9])")'
+    - 'userAgent.matches("^Mozilla/[1-4]")'
+
+
+# Rules are checked sequentially in order, from top to bottom
+rules:
+  - name: allow-well-known-resources
+    conditions:
+      - '($is-well-known-asset)'
+    action: pass
+
+  - name: allow-static-resources
+    conditions:
+      - '($is-static-asset)'
+    action: pass
+
+  - name: desired-crawlers
+    conditions:
+      - *is-bot-googlebot
+      - *is-bot-bingbot
+      - *is-bot-duckduckbot
+      - *is-bot-kagibot
+      - *is-bot-qwantbot
+      - *is-bot-yandexbot
+    action: pass
+
+  # Matches private networks and localhost.
+  # Uncomment this if you want to let your own tools this way
+  #  - name: allow-private-networks
+  #    conditions:
+  #      # Allows localhost and private networks CIDR
+  #      - *is-network-localhost
+  #      - *is-network-private
+  #    action: pass
+
+  - name: undesired-crawlers
+    conditions:
+      - '($is-headless-chromium)'
+      - 'userAgent.startsWith("Lightpanda/")'
+      - 'userAgent.startsWith("masscan/")'
+      # Typo'd opera botnet
+      - 'userAgent.matches("^Opera/[0-9.]+\\.\\(")'
+      # AI bullshit stuff, they do not respect robots.txt even while they read it
+      # TikTok Bytedance AI training
+      - 'userAgent.contains("Bytedance") || userAgent.contains("Bytespider") || userAgent.contains("TikTokSpider")'
+      # Meta AI training; The Meta-ExternalAgent crawler crawls the web for use cases such as training AI models or improving products by indexing content directly.
+      - 'userAgent.contains("meta-externalagent/") || userAgent.contains("meta-externalfetcher/") || userAgent.contains("FacebookBot")'
+      # Anthropic AI training and usage
+      - 'userAgent.contains("ClaudeBot") || userAgent.contains("Claude-User")|| userAgent.contains("Claude-SearchBot")'
+      # Common Crawl AI crawlers
+      - 'userAgent.contains("CCBot")'
+      # ChatGPT AI crawlers https://platform.openai.com/docs/bots
+      - 'userAgent.contains("GPTBot") || userAgent.contains("OAI-SearchBot") || userAgent.contains("ChatGPT-User")'
+      # Other AI crawlers
+      - 'userAgent.contains("Amazonbot") || userAgent.contains("Google-Extended") || userAgent.contains("PanguBot") || userAgent.contains("AI2Bot") || userAgent.contains("Diffbot") || userAgent.contains("cohere-training-data-crawler") || userAgent.contains("Applebot-Extended")'
+      # SEO / Ads and marketing
+      - 'userAgent.contains("BLEXBot")'
+    action: drop
+
+  - name: unknown-crawlers
+    conditions:
+      # No user agent set
+      - 'userAgent == ""'
+    action: deny
+
+  # check a sequence of challenges
+  - name: suspicious-crawlers
+    conditions: ['($is-suspicious-crawler)']
+    action: none
+    children:
+      - name: 0
+        action: check
+        settings:
+          challenges: [js-refresh]
+      - name: 1
+        action: check
+        settings:
+          challenges: [preload-link, resource-load]
+      - name: 2
+        action: check
+        settings:
+          challenges: [header-refresh]
+
+  - name: homesite
+    conditions:
+      - 'path == "/"'
+      - 'path == "/links/~ssh"'
+      - 'path == "/links/~pgp"'
+    action: pass
+
+  # check DNSBL and serve harder challenges
+  # todo: make this specific to score
+  - name: undesired-dnsbl
+    action: check
+    settings:
+      challenges: [dnsbl]
+      # if DNSBL fails, check additional challenges
+      fail: check
+      fail-settings:
+        challenges: [js-refresh]
+
+  - name: suspicious-fetchers
+    action: check
+    settings:
+      challenges: [js-refresh]
+    conditions:
+      - 'userAgent.contains("facebookexternalhit/") || userAgent.contains("facebookcatalog/")'
+
+  # Allow PUT/DELETE/PATCH/POST requests in general
+  - name: non-get-request
+    action: pass
+    conditions:
+      - '!(method == "HEAD" || method == "GET")'
+
+  # Enable fetching OpenGraph and other tags from backend on these paths
+  - name: enable-meta-tags
+    action: context
+    settings:
+      context-set:
+        # Map OpenGraph or similar <meta> tags back to the reply, even if denied/challenged
+        proxy-meta-tags: "true"
+
+      # Set additional response headers
+      #response-headers:
+      # X-Clacks-Overhead:
+      #  - GNU Terry Pratchett
+
+  - name: plaintext-browser
+    action: challenge
+    settings:
+      challenges: [meta-refresh, cookie]
+    conditions:
+      - 'userAgent.startsWith("Lynx/")'
+
+  # Uncomment this rule out to challenge tool-like user agents
+  #- name: standard-tools
+  #  action: challenge
+  #  settings:
+  #    challenges: [cookie]
+  #  conditions:
+  #    - '($is-generic-robot-ua)'
+  #    - '($is-tool-ua)'
+  #    - '!($is-generic-browser)'
+
+  - name: standard-browser
+    action: challenge
+    settings:
+      challenges: [preload-link, meta-refresh, resource-load, js-refresh, js-pow-sha256]
+    conditions:
+      - '($is-generic-browser)'
+
+# If end of rules is reached, default is PASS
diff --git a/bundles/go-away/files/config/snippets/bot-betterstack.yml b/bundles/go-away/files/config/snippets/bot-betterstack.yml
new file mode 100644
index 0000000..41bd2a1
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-betterstack.yml
@@ -0,0 +1,8 @@
+networks:
+  betterstack:
+    - url: https://uptime.betterstack.com/ips-by-cluster.json
+      jq-path: '.[] | .[]'
+
+conditions:
+  is-bot-betterstack:
+      - &is-bot-betterstack '((userAgent.startsWith("Better Stack Better Uptime Bot") || userAgent.startsWith("Better Uptime Bot") || userAgent == "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.17 Safari/537.36")) && remoteAddress.network("betterstack")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/bot-bingbot.yml b/bundles/go-away/files/config/snippets/bot-bingbot.yml
new file mode 100644
index 0000000..98fb3db
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-bingbot.yml
@@ -0,0 +1,8 @@
+networks:
+  bingbot:
+    - url: https://www.bing.com/toolbox/bingbot.json
+      jq-path: '(.prefixes[] | select(has("ipv4Prefix")) | .ipv4Prefix), (.prefixes[] | select(has("ipv6Prefix")) | .ipv6Prefix)'
+
+conditions:
+  is-bot-bingbot:
+      - &is-bot-bingbot 'userAgent.contains("+http://www.bing.com/bingbot.htm") && remoteAddress.network("bingbot")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/bot-duckduckbot.yml b/bundles/go-away/files/config/snippets/bot-duckduckbot.yml
new file mode 100644
index 0000000..b5f45e1
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-duckduckbot.yml
@@ -0,0 +1,8 @@
+networks:
+  duckduckbot:
+    - url: https://duckduckgo.com/duckduckgo-help-pages/results/duckduckbot
+      regex: "<li><div>(?P<prefix>[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)</div></li>"
+
+conditions:
+  is-bot-duckduckbot:
+      - &is-bot-duckduckbot 'userAgent.contains("+http://duckduckgo.com/duckduckbot.html") && remoteAddress.network("duckduckbot")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/bot-googlebot.yml b/bundles/go-away/files/config/snippets/bot-googlebot.yml
new file mode 100644
index 0000000..da26884
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-googlebot.yml
@@ -0,0 +1,8 @@
+networks:
+  googlebot:
+    - url: https://developers.google.com/static/search/apis/ipranges/googlebot.json
+      jq-path: '(.prefixes[] | select(has("ipv4Prefix")) | .ipv4Prefix), (.prefixes[] | select(has("ipv6Prefix")) | .ipv6Prefix)'
+
+conditions:
+  is-bot-googlebot:
+      - &is-bot-googlebot '(userAgent.contains("+http://www.google.com/bot.html") || userAgent.contains("Google-PageRenderer") || userAgent.contains("Google-InspectionTool") || userAgent.contains("Googlebot")) && remoteAddress.network("googlebot")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/bot-kagibot.yml b/bundles/go-away/files/config/snippets/bot-kagibot.yml
new file mode 100644
index 0000000..8edb3c3
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-kagibot.yml
@@ -0,0 +1,8 @@
+networks:
+  kagibot:
+    - url: https://kagi.com/bot
+      regex: "\\n(?P<prefix>[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+) "
+
+conditions:
+  is-bot-kagibot:
+      - &is-bot-kagibot 'userAgent.contains("+https://kagi.com/bot") && remoteAddress.network("kagibot")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/bot-qwantbot.yml b/bundles/go-away/files/config/snippets/bot-qwantbot.yml
new file mode 100644
index 0000000..095e1be
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-qwantbot.yml
@@ -0,0 +1,8 @@
+networks:
+  qwantbot:
+    - url: https://help.qwant.com/wp-content/uploads/sites/2/2025/01/qwantbot.json
+      jq-path: '(.prefixes[] | select(has("ipv4Prefix")) | .ipv4Prefix), (.prefixes[] | select(has("ipv6Prefix")) | .ipv6Prefix)'
+
+conditions:
+  is-bot-qwantbot:
+      - &is-bot-qwantbot 'userAgent.contains("+https://help.qwant.com/bot/") && remoteAddress.network("qwantbot")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/bot-uptimerobot.yml b/bundles/go-away/files/config/snippets/bot-uptimerobot.yml
new file mode 100644
index 0000000..848424c
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-uptimerobot.yml
@@ -0,0 +1,8 @@
+networks:
+  uptimerobot:
+    - url: https://uptimerobot.com/inc/files/ips/IPv4andIPv6.txt
+      regex: "(?P<prefix>[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+(/[0-9]+)?|[0-9a-f:]+:.+)"
+
+conditions:
+  is-bot-uptimerobot:
+      - &is-bot-uptimerobot 'userAgent.contains("http://www.uptimerobot.com/") && remoteAddress.network("uptimerobot")'
diff --git a/bundles/go-away/files/config/snippets/bot-yandexbot.yml b/bundles/go-away/files/config/snippets/bot-yandexbot.yml
new file mode 100644
index 0000000..981ee42
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/bot-yandexbot.yml
@@ -0,0 +1,24 @@
+networks:
+  yandexbot:
+    # todo: detected as bot
+    # - url: https://yandex.com/ips
+    #  regex: "<span>(?P<prefix>(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)|([0-9a-f:]+::))/[0-9]+)[ \\\\t]*</span><br/>"
+    - prefixes:
+      - "5.45.192.0/18"
+      - "5.255.192.0/18"
+      - "37.9.64.0/18"
+      - "37.140.128.0/18"
+      - "77.88.0.0/18"
+      - "84.252.160.0/19"
+      - "87.250.224.0/19"
+      - "90.156.176.0/22"
+      - "93.158.128.0/18"
+      - "95.108.128.0/17"
+      - "141.8.128.0/18"
+      - "178.154.128.0/18"
+      - "185.32.187.0/24"
+      - "2a02:6b8::/29"
+
+conditions:
+  is-bot-yandexbot:
+      - &is-bot-yandexbot 'userAgent.contains("+http://yandex.com/bots") && remoteAddress.network("yandexbot")'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/challenge-dnsbl.yml b/bundles/go-away/files/config/snippets/challenge-dnsbl.yml
new file mode 100644
index 0000000..893116c
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/challenge-dnsbl.yml
@@ -0,0 +1,6 @@
+challenges:
+  dnsbl:
+    runtime: dnsbl
+    parameters:
+      dnsbl-decay: 1h
+      dnsbl-timeout: 1s
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/challenge-js-pow-sha256.yml b/bundles/go-away/files/config/snippets/challenge-js-pow-sha256.yml
new file mode 100644
index 0000000..78f57f0
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/challenge-js-pow-sha256.yml
@@ -0,0 +1,15 @@
+challenges:
+  js-pow-sha256:
+    runtime: js
+    parameters:
+      # specifies the folder path that assets are under
+      # can be either embedded or external path
+      # defaults to name of challenge
+      path: "js-pow-sha256"
+      # needs to be under static folder
+      js-loader: load.mjs
+      # needs to be under runtime folder
+      wasm-runtime: runtime.wasm
+      wasm-runtime-settings:
+        difficulty: 20
+      verify-probability: 0.1
diff --git a/bundles/go-away/files/config/snippets/challenge-js-refresh.yml b/bundles/go-away/files/config/snippets/challenge-js-refresh.yml
new file mode 100644
index 0000000..73ba729
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/challenge-js-refresh.yml
@@ -0,0 +1,6 @@
+challenges:
+  js-refresh:
+    # Challenges with a redirect via window.location (requires HTML parsing and JavaScript logic)
+    runtime: "refresh"
+    parameters:
+      refresh-via: "javascript"
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/challenges-non-js.yml b/bundles/go-away/files/config/snippets/challenges-non-js.yml
new file mode 100644
index 0000000..b8aff89
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/challenges-non-js.yml
@@ -0,0 +1,28 @@
+challenges:
+  # Challenges with a cookie, self redirect (non-JS, requires HTTP parsing)
+  cookie:
+    runtime: "cookie"
+
+  # Challenges with a redirect via Link header with rel=preload and early hints (non-JS, requires HTTP parsing, fetching and logic)
+  # Works on HTTP/2 and above!
+  preload-link:
+    condition: '"Sec-Fetch-Mode" in headers && headers["Sec-Fetch-Mode"] == "navigate"'
+    runtime: "preload-link"
+    parameters:
+      preload-early-hint-deadline: 2s
+
+  # Challenges with a redirect via Refresh header (non-JS, requires HTTP parsing and logic)
+  header-refresh:
+    runtime: "refresh"
+    parameters:
+      refresh-via: "header"
+
+  # Challenges with a redirect via Refresh meta (non-JS, requires HTML parsing and logic)
+  meta-refresh:
+    runtime: "refresh"
+    parameters:
+      refresh-via: "meta"
+
+  # Challenges with loading a random CSS or image document (non-JS, requires HTML parsing and logic)
+  resource-load:
+    runtime: "resource-load"
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/conditions-generic.yml b/bundles/go-away/files/config/snippets/conditions-generic.yml
new file mode 100644
index 0000000..4e64cca
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/conditions-generic.yml
@@ -0,0 +1,56 @@
+conditions:
+  is-well-known-asset:
+    # general txt files or scraper
+    - 'path == "/robots.txt" || path == "/security.txt"'
+
+    # ads txt files
+    - 'path == "/app-ads.txt" || path == "/ads.txt"'
+
+    # generally requested by browsers
+    - 'path == "/favicon.ico"'
+
+    # used by some applications
+    - 'path == "/crossdomain.xml"'
+
+    # well-known paths
+    - 'path.startsWith("/.well-known/")'
+
+  is-git-ua:
+    - 'userAgent.startsWith("git/") || userAgent.contains("libgit")'
+    - 'userAgent.startsWith("go-git")'
+    - 'userAgent.startsWith("JGit/") || userAgent.startsWith("JGit-")'
+    # Golang proxy and initial fetch
+    - 'userAgent.startsWith("GoModuleMirror/")'
+    - 'userAgent.startsWith("Go-http-client/") && "go-get" in query && query["go-get"] == "1"'
+    - '"Git-Protocol" in headers && headers["Git-Protocol"] == "version=2"'
+
+  is-generic-browser:
+    - 'userAgent.startsWith("Mozilla/") || userAgent.startsWith("Opera/")'
+
+  is-generic-robot-ua:
+    - 'userAgent.matches("compatible[;)]") && !userAgent.contains("Trident/")'
+    - 'userAgent.matches("\\+https?://")'
+    - 'userAgent.contains("@")'
+    - 'userAgent.matches("[bB]ot/[0-9]")'
+
+  is-tool-ua:
+    - 'userAgent.startsWith("python-requests/")'
+    - 'userAgent.startsWith("Python-urllib/")'
+    - 'userAgent.startsWith("python-httpx/")'
+    - 'userAgent.contains("aoihttp/")'
+    - 'userAgent.startsWith("http.rb/")'
+    - 'userAgent.startsWith("curl/")'
+    - 'userAgent.startsWith("Wget/")'
+    - 'userAgent.startsWith("libcurl/")'
+    - 'userAgent.startsWith("okhttp/")'
+    - 'userAgent.startsWith("Java/")'
+    - 'userAgent.startsWith("Apache-HttpClient//")'
+    - 'userAgent.startsWith("Go-http-client/")'
+    - 'userAgent.startsWith("node-fetch/")'
+    - 'userAgent.startsWith("reqwest/")'
+
+  # Checks to detect a headless chromium via headers only
+  is-headless-chromium:
+    - 'userAgent.contains("HeadlessChrome") || userAgent.contains("HeadlessChromium")'
+    - '"Sec-Ch-Ua" in headers && (headers["Sec-Ch-Ua"].contains("HeadlessChrome") || headers["Sec-Ch-Ua"].contains("HeadlessChromium"))'
+    #- '(userAgent.contains("Chrome/") || userAgent.contains("Chromium/")) && (!("Accept-Language" in headers) || !("Accept-Encoding" in headers))'
\ No newline at end of file
diff --git a/bundles/go-away/files/config/snippets/networks-other.yml b/bundles/go-away/files/config/snippets/networks-other.yml
new file mode 100644
index 0000000..c5ffdda
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/networks-other.yml
@@ -0,0 +1,37 @@
+networks:
+  aws-cloud:
+    - url: https://ip-ranges.amazonaws.com/ip-ranges.json
+      jq-path: '(.prefixes[] | select(has("ip_prefix")) | .ip_prefix), (.prefixes[] | select(has("ipv6_prefix")) | .ipv6_prefix)'
+  google-cloud:
+    - url: https://www.gstatic.com/ipranges/cloud.json
+      jq-path: '(.prefixes[] | select(has("ipv4Prefix")) | .ipv4Prefix), (.prefixes[] | select(has("ipv6Prefix")) | .ipv6Prefix)'
+  oracle-cloud:
+    - url: https://docs.oracle.com/en-us/iaas/tools/public_ip_ranges.json
+      jq-path: '.regions[] | .cidrs[] | .cidr'
+  azure-cloud:
+    # todo: https://www.microsoft.com/en-us/download/details.aspx?id=56519 does not provide direct JSON
+    - url: https://raw.githubusercontent.com/femueller/cloud-ip-ranges/refs/heads/master/microsoft-azure-ip-ranges.json
+      jq-path: '.values[] | .properties.addressPrefixes[]'
+
+  digitalocean:
+    - url: https://www.digitalocean.com/geo/google.csv
+      regex: "(?P<prefix>(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)|([0-9a-f:]+::))/[0-9]+),"
+  linode:
+    - url: https://geoip.linode.com/
+      regex: "(?P<prefix>(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)|([0-9a-f:]+::))/[0-9]+),"
+  vultr:
+    - url: "https://geofeed.constant.com/?json"
+      jq-path: '.subnets[] | .ip_prefix'
+  cloudflare:
+    - url: https://www.cloudflare.com/ips-v4
+      regex: "(?P<prefix>[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+/[0-9]+)"
+    - url: https://www.cloudflare.com/ips-v6
+      regex: "(?P<prefix>[0-9a-f:]+::/[0-9]+)"
+
+  icloud-private-relay:
+    - url: https://mask-api.icloud.com/egress-ip-ranges.csv
+      regex: "(?P<prefix>(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)|([0-9a-f:]+::))/[0-9]+),"
+  tunnelbroker-relay:
+    # HE Tunnelbroker
+    - url: https://tunnelbroker.net/export/google
+      regex: "(?P<prefix>([0-9a-f:]+::)/[0-9]+),"
diff --git a/bundles/go-away/files/config/snippets/networks-private.yml b/bundles/go-away/files/config/snippets/networks-private.yml
new file mode 100644
index 0000000..4d81b6a
--- /dev/null
+++ b/bundles/go-away/files/config/snippets/networks-private.yml
@@ -0,0 +1,22 @@
+networks:
+  localhost:
+    # localhost and loopback addresses
+    - prefixes:
+      - "127.0.0.0/8"
+      - "::1/128"
+  private:
+    # Private network CIDR blocks
+    - prefixes:
+        # private networks
+        - "10.0.0.0/8"
+        - "172.16.0.0/12"
+        - "192.168.0.0/16"
+        - "fc00::/7"
+        # CGNAT
+        - "100.64.0.0/10"
+
+conditions:
+  is-network-localhost:
+    - &is-network-localhost 'remoteAddress.network("localhost")'
+  is-network-private:
+    - &is-network-private 'remoteAddress.network("private")'
\ No newline at end of file
diff --git a/bundles/go-away/files/go-away.run b/bundles/go-away/files/go-away.run
index 376fbb2..5e4384e 100755
--- a/bundles/go-away/files/go-away.run
+++ b/bundles/go-away/files/go-away.run
@@ -3,16 +3,12 @@ set -eux
 
 cd /var/lib/go-away || exit 1
 
-test -d examples
-test -d examples/snippets
-test -f examples/generic.yml
+test -d config/
+test -f config/config.yml
+test -f config/policy.yml
+test -d config/snippets/
 
 exec ./go-away \
-	--bind 0.0.0.0:${node.metadata.get("go-away/port")} \
-	--backend gzr.im="http://${node.metadata.get("container_gateway", "10.89.0.1")}:${node.metadata.get("www/gzr-im/port")}" \
-	--backend gnzler.de="http://${node.metadata.get("container_gateway", "10.89.0.1")}:${node.metadata.get("www/gzr-im/port")}" \
-	--backend gnzler.io="http://${node.metadata.get("container_gateway", "10.89.0.1")}:${node.metadata.get("www/gzr-im/port")}" \
-	--backend gonca.me="http://${node.metadata.get("container_gateway","10.89.0.1")}:${node.metadata.get("www/gonca-me/port")}" \
-	--policy examples/generic.yml \
-	--policy-snippets examples/snippets/ \
-	--challenge-template anubis
+	--config config/config.yml \
+	--policy config/policy.yml \
+	--policy-snippets config/snippets/
diff --git a/bundles/go-away/files/traefik.yaml b/bundles/go-away/files/traefik.yaml
index e44a842..a8fcb9d 100644
--- a/bundles/go-away/files/traefik.yaml
+++ b/bundles/go-away/files/traefik.yaml
@@ -4,6 +4,10 @@ http:
       loadBalancer:
         servers:
           - url: http://${node.metadata.get("container_gateway", "10.89.0.1")}:${node.metadata.get("go-away/port")}
+    go-away-metrics:
+      loadBalancer:
+        servers:
+          - url: http://${node.metadata.get("container_gateway", "10.89.0.1")}:${node.metadata.get("go-away-metrics/port")}
 
   routers:
     gzr-im:
@@ -26,3 +30,11 @@ http:
         - tls-redirect@file
       tls:
         certResolver: le
+
+    go-away-metrics:
+      rule: Path(`/metrics/go-away`)
+      entryPoints:
+        - metrics
+      service: go-away-metrics
+      middlewares:
+        - replacepath-metrics@docker
diff --git a/bundles/go-away/items.py b/bundles/go-away/items.py
index 904b922..7761734 100644
--- a/bundles/go-away/items.py
+++ b/bundles/go-away/items.py
@@ -1,3 +1,4 @@
+from os.path import join
 
 # local_deploy = {
 #     "/usr/bin/go-away": {
@@ -8,34 +9,51 @@
 #     },
 # }
 
-directories = {
-    "/var/lib/go-away": {
-        "mode": "0775",
-        "group": "wheel",
-        "owner": "nobody",
-    },
-}
-
 files = {
-    "/etc/service/go-away.run": {
-        "content_type": "mako",
-        "mode": "0755",
-        "triggers": {"svc_s6rc:go-away:restart"},
+    "/etc/s6-rc/services/go-away.run": {
+        'content_type': "mako",
+        'mode': "0755",
+        'triggers': {
+            "svc_s6rc:go-away:restart"
+        },
     },
-
     "/etc/traefik/go-away.yaml": {
-        "content_type": "mako",
-        "source": "traefik.yaml",
-        "needs": {
+        'content_type': "mako",
+        'source': "traefik.yaml",
+        'needs': {
             "bundle:traefik",
         },
     },
+    "/var/lib/go-away/config/config.yml": { 'content_type': "mako" },
+    "/var/lib/go-away/go-away": {
+        'content_type': "any",
+        'mode': "0755",
+    },
+}
 
+directories = {
+    "/var/lib/go-away": {
+        'mode': "0775",
+        'group': "wheel",
+        'owner': "nobody",
+        'purge': True,
+    },
 }
 
+repo.libs.gen.merge(files, repo.libs.gen.tree(
+    join(repo.bundles_dir, "go-away", "files", "config"),
+    keep_parent=True,
+    target_dir="/var/lib/go-away/",
+    common={
+        'triggers': {
+            "svc_s6rc:go-away:restart"
+        },
+    },
+))
+
 svc_s6rc = {
     "go-away": {
-        "bundle": "services",
-        "generate": "service",
+        'bundle': "services",
+        'generate': "service",
     }
 }
diff --git a/bundles/go-away/metadata.py b/bundles/go-away/metadata.py
index 5160f64..b9d8021 100644
--- a/bundles/go-away/metadata.py
+++ b/bundles/go-away/metadata.py
@@ -1,4 +1,7 @@
 defaults = {
+    "metrics": {
+        "go-away": {},
+    },
     "apk": {
         "packages": {},
     },
diff --git a/nodes.py b/nodes.py
index 34c32b9..f85e3c2 100644
--- a/nodes.py
+++ b/nodes.py
@@ -32,12 +32,11 @@ nodes = {
                     "port": "20107",
                 },
             },
-            "go-away": {
-                "port": "20105",
-            },
             "glance": {
                 "port": "20106",
             },
+            "go-away": { 'port': "20199" },
+            "go-away-metrics": { 'port': "20198" },
         },
         "bundles": {
             "firewall",