From a7d558f5c5cf2f0c244aa87c54089a1b857cb010 Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Tue, 20 Oct 2026 23:35:09 +0400 Subject: [PATCH] Publish a no-unsolicited-messages policy The .xyz registry put the domain on serverHold after SURBL listed it in its ABUSE list. SURBL only delists a site that links to a published policy against advertising it by unsolicited messages, and a privacy policy does not count. Add the policy at /:locale/policy in all three languages, linked from the footer and listed in the sitemap, and the same text in the README so it can be read on GitHub while the domain does not resolve. Reports go to the asxp.io contact form; the site itself has no email. Assisted-by: Claude:opus-5-5 --- CLAUDE.md | 3 +++ README.md | 12 ++++++++++++ app.rb | 17 ++++++++++++++++- lib/bingo.rb | 21 ++++++++++++++++++--- test/bingo_test.rb | 14 +++++++++++++- views/_footer.erb | 2 +- views/policy.erb | 17 +++++++++++++++++ 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 views/policy.erb diff --git a/CLAUDE.md b/CLAUDE.md index 1060ed0..3144525 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,6 +53,9 @@ renderings only for unbuilt sites) and credit format. degraded, down (nothing answered for two buckets), unknown (worker stale, or under half the controls answering). Not incidents: never cross out a cell from this alone. +- `/:locale/policy`: the no-unsolicited-messages policy, also in + `README.md`. Blocklists (SURBL) require a published one to delist; keep + both copies in step. - `config.ru`: `www.` → `BINGO_HOST` 301. - `views/`: `layout.erb` (CSS and SEO head), `index.erb` (card, provider filter, dialogs), `dc.erb`, partials. `assets/`: icons. diff --git a/README.md b/README.md index 53a732e..b22e51c 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,16 @@ of Claude (Anthropic). Part of the list came from the Credit "Datacenter Bingo (datacenterbingo.xyz)"; the full attribution line is in the file. +## No unsolicited messages + +datacenterbingo.xyz is never advertised by unsolicited email, SMS, +messenger, forum or social media messages. Neither we nor any third party +may promote the site this way. The site sends no email and has no mailing +list, newsletter or contact form, and it carries no ads, affiliate programs +or paid promotion. + +If you received a message linking to this site, we did not send it. Please +report it via [asxp.io/contact](https://asxp.io/contact). The same policy +is on the site at [/en/policy](https://datacenterbingo.xyz/en/policy). + Development: `nix develop`, then `bin/test`. See `CLAUDE.md`. diff --git a/app.rb b/app.rb index 8afc537..70ccd60 100644 --- a/app.rb +++ b/app.rb @@ -269,7 +269,8 @@ class BingoWeb < Sinatra::Base cache_control :public, max_age: 3600 dcs = Bingo::Datacenter.order(:position, :id).eager(:incidents).all @entries = [[->(l) { "/#{l}" }, Bingo::Board.load.updated_on, '/'], - [->(l) { "/#{l}/status" }, nil, '/en/status']] + + [->(l) { "/#{l}/status" }, nil, '/en/status'], + [->(l) { "/#{l}/policy" }, nil, '/en/policy']] + dcs.map { |dc| [->(l) { dc_path(dc, l) }, [dc.updated_at, *dc.incidents.map(&:updated_at)].compact.max] } erb :sitemap, layout: false end @@ -326,6 +327,20 @@ class BingoWeb < Sinatra::Base erb :status end + # Anti-spam policy; blocklists (SURBL) ask for a published one. + get %r{/(ru|en|uk)/policy} do |locale| + @locale = locale + @title = t(:policy_title) + @desc = t(:policy_desc) + @path_for = ->(l) { "/#{l}/policy" } + @x_default = '/en/policy' + url = "#{base_url}/#{locale}/policy" + @ld = JSON.generate('@context' => 'https://schema.org', '@type' => 'WebPage', '@id' => url, 'url' => url, + 'name' => @title, 'description' => @desc, 'inLanguage' => locale).gsub(' '*' diff --git a/lib/bingo.rb b/lib/bingo.rb index 33fc3d5..635df03 100644 --- a/lib/bingo.rb +++ b/lib/bingo.rb @@ -86,7 +86,12 @@ module Bingo status_cols: { 'dc' => 'ЦОД', 'state' => 'Сейчас', 'hours' => '24 часа', 'rtt' => 'мс', 'targets' => 'адр.' }, status_dc: 'Доступность сети', status_hour: '%{h} ч назад: %{p}', status_desc: 'Доступность крупнейших дата-центров России онлайн: ежеминутные сетевые проверки IXcellerate, Selectel, DataPro, Key Point и других, история за 24 часа и открытые данные.', - status_probes: 'Что проверяем', status_controls_list: 'Контрольные узлы в России', status_no_answer: 'не отвечает', status_source: 'почему этот адрес' + status_probes: 'Что проверяем', status_controls_list: 'Контрольные узлы в России', status_no_answer: 'не отвечает', status_source: 'почему этот адрес', + policy_link: 'Без спама', policy_title: 'Без нежелательных рассылок | Бинго ЦОДов', + policy_desc: 'Бинго ЦОДов никогда не рекламируется нежелательными сообщениями. Сайт не отправляет писем и не ведёт рассылок.', + policy_text: ['datacenterbingo.xyz никогда не рекламируется нежелательными письмами, SMS, сообщениями в мессенджерах, на форумах или в соцсетях. Ни мы, ни кто-либо другой не вправе продвигать сайт таким образом.', + 'Сайт не отправляет писем, у него нет рассылок, подписок и форм обратной связи. На нём нет рекламы, партнёрских программ и платного продвижения.', + 'Если вы получили сообщение со ссылкой на этот сайт, его отправили не мы. Пожалуйста, сообщите о нём через %{link}.'] }, 'en' => { seo_title: 'Datacenter Bingo — strikes, fires and outages at Russian datacenters', @@ -132,7 +137,12 @@ module Bingo status_cols: { 'dc' => 'Datacenter', 'state' => 'Now', 'hours' => '24 hours', 'rtt' => 'ms', 'targets' => 'addr.' }, status_dc: 'Network reachability', status_hour: '%{h} h ago: %{p}', status_desc: "Live reachability of Russia's largest datacenters: minute-by-minute network checks of IXcellerate, Selectel, DataPro, Key Point and others, 24-hour history and open data.", - status_probes: 'What we check', status_controls_list: 'Control hosts in Russia', status_no_answer: 'no answer', status_source: 'why this address' + status_probes: 'What we check', status_controls_list: 'Control hosts in Russia', status_no_answer: 'no answer', status_source: 'why this address', + policy_link: 'No-spam policy', policy_title: 'No unsolicited messages | Datacenter Bingo', + policy_desc: 'Datacenter Bingo is never advertised by unsolicited messages. The site sends no email and runs no mailing list.', + policy_text: ['datacenterbingo.xyz is never advertised by unsolicited email, SMS, messenger, forum or social media messages. Neither we nor any third party may promote the site this way.', + 'The site sends no email and has no mailing list, newsletter or contact form. It carries no ads, affiliate programs or paid promotion.', + 'If you received a message linking to this site, we did not send it. Please report it via %{link}.'] }, 'uk' => { seo_title: 'Бінго ЦОДів — атаки, пожежі та аварії дата-центрів Росії', @@ -178,7 +188,12 @@ module Bingo status_cols: { 'dc' => 'ЦОД', 'state' => 'Зараз', 'hours' => '24 години', 'rtt' => 'мс', 'targets' => 'адр.' }, status_dc: 'Доступність мережі', status_hour: '%{h} год тому: %{p}', status_desc: 'Доступність найбільших дата-центрів Росії онлайн: щохвилинні мережеві перевірки IXcellerate, Selectel, DataPro, Key Point та інших, історія за 24 години та відкриті дані.', - status_probes: 'Що перевіряємо', status_controls_list: 'Контрольні вузли в Росії', status_no_answer: 'не відповідає', status_source: 'чому ця адреса' + status_probes: 'Що перевіряємо', status_controls_list: 'Контрольні вузли в Росії', status_no_answer: 'не відповідає', status_source: 'чому ця адреса', + policy_link: 'Без спаму', policy_title: 'Без небажаних розсилок | Бінго ЦОДів', + policy_desc: 'Бінго ЦОДів ніколи не рекламується небажаними повідомленнями. Сайт не надсилає листів і не веде розсилок.', + policy_text: ['datacenterbingo.xyz ніколи не рекламується небажаними листами, SMS, повідомленнями в месенджерах, на форумах чи в соцмережах. Ні ми, ні будь-хто інший не має права просувати сайт у такий спосіб.', + 'Сайт не надсилає листів, не має розсилок, підписок і форм зворотного зв’язку. На ньому немає реклами, партнерських програм і платного просування.', + 'Якщо ви отримали повідомлення з посиланням на цей сайт, його надіслали не ми. Будь ласка, повідомте про нього через %{link}.'] } }.freeze diff --git a/test/bingo_test.rb b/test/bingo_test.rb index f3cf031..b6cc5a9 100644 --- a/test/bingo_test.rb +++ b/test/bingo_test.rb @@ -150,6 +150,17 @@ class BingoTest < Minitest::Test assert_includes last_response.body, 'hackyourmom' end + def test_policy_page_in_every_locale_and_footer + { 'en' => 'never advertised', 'ru' => 'никогда не рекламируется', 'uk' => 'ніколи не рекламується' }.each do |l, text| + get "/#{l}/policy" + assert last_response.ok? + assert_includes last_response.body, text + assert_includes last_response.body, 'asxp.io/contact' + end + get '/en' + assert_includes last_response.body, 'No-spam policy' + end + def test_robots_and_sitemap_are_the_bingos_own get '/robots.txt' assert last_response.ok? @@ -161,7 +172,8 @@ class BingoTest < Minitest::Test %w[ru en uk].each { |l| assert_includes last_response.body, "https://example.org/#{l}/dc/dc-25" } assert_includes last_response.body, '' %w[ru en uk].each { |l| assert_includes last_response.body, "https://example.org/#{l}/status" } - assert_equal 6, last_response.body.scan('hreflang="x-default"').size # card and status URLs only + %w[ru en uk].each { |l| assert_includes last_response.body, "https://example.org/#{l}/policy" } + assert_equal 9, last_response.body.scan('hreflang="x-default"').size # card, status and policy URLs only assert_includes last_response.body, 'hreflang="x-default"' refute_includes last_response.body, 'changefreq' assert_match %r{\n \d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ\n <%= t(:footer) %>

<%= t(:credits_ai) %> <%== format(Rack::Utils.escape_html(t(:credits_hym)), link: 'hackyourmom') %>

-

RSS · JSON Feed · <%= t(:open_data) %> · <%= t(:status_link) %>

+

RSS · JSON Feed · <%= t(:open_data) %> · <%= t(:status_link) %> · <%= t(:policy_link) %>

© <%= Date.today.year %> asxp.io · <%= t(:source_code) %>

diff --git a/views/policy.erb b/views/policy.erb new file mode 100644 index 0000000..193f9c2 --- /dev/null +++ b/views/policy.erb @@ -0,0 +1,17 @@ +
+
+

<%= t(:policy_link) %>

+ +
+ + + <% t(:policy_text).each do |para| %> +

<%== format(Rack::Utils.escape_html(para), link: 'asxp.io/contact') %>

+ <% end %> + + <%== erb :_footer, layout: false %> +