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
This commit is contained in:
parent
5e36cc6a95
commit
a7d558f5c5
7 changed files with 80 additions and 6 deletions
|
|
@ -53,6 +53,9 @@ renderings only for unbuilt sites) and credit format.
|
||||||
degraded, down (nothing answered for two buckets), unknown (worker stale,
|
degraded, down (nothing answered for two buckets), unknown (worker stale,
|
||||||
or under half the controls answering). Not incidents: never cross out a
|
or under half the controls answering). Not incidents: never cross out a
|
||||||
cell from this alone.
|
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.
|
- `config.ru`: `www.` → `BINGO_HOST` 301.
|
||||||
- `views/`: `layout.erb` (CSS and SEO head), `index.erb` (card, provider
|
- `views/`: `layout.erb` (CSS and SEO head), `index.erb` (card, provider
|
||||||
filter, dialogs), `dc.erb`, partials. `assets/`: icons.
|
filter, dialogs), `dc.erb`, partials. `assets/`: icons.
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -22,4 +22,16 @@ of Claude (Anthropic). Part of the list came from the
|
||||||
Credit "Datacenter Bingo (datacenterbingo.xyz)"; the full attribution
|
Credit "Datacenter Bingo (datacenterbingo.xyz)"; the full attribution
|
||||||
line is in the file.
|
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`.
|
Development: `nix develop`, then `bin/test`. See `CLAUDE.md`.
|
||||||
|
|
|
||||||
17
app.rb
17
app.rb
|
|
@ -269,7 +269,8 @@ class BingoWeb < Sinatra::Base
|
||||||
cache_control :public, max_age: 3600
|
cache_control :public, max_age: 3600
|
||||||
dcs = Bingo::Datacenter.order(:position, :id).eager(:incidents).all
|
dcs = Bingo::Datacenter.order(:position, :id).eager(:incidents).all
|
||||||
@entries = [[->(l) { "/#{l}" }, Bingo::Board.load.updated_on, '/'],
|
@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] }
|
dcs.map { |dc| [->(l) { dc_path(dc, l) }, [dc.updated_at, *dc.incidents.map(&:updated_at)].compact.max] }
|
||||||
erb :sitemap, layout: false
|
erb :sitemap, layout: false
|
||||||
end
|
end
|
||||||
|
|
@ -326,6 +327,20 @@ class BingoWeb < Sinatra::Base
|
||||||
erb :status
|
erb :status
|
||||||
end
|
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('</', '<\/')
|
||||||
|
cache_control :public, max_age: 3600
|
||||||
|
erb :policy
|
||||||
|
end
|
||||||
|
|
||||||
get '/status.json' do
|
get '/status.json' do
|
||||||
content_type 'application/json', charset: 'utf-8'
|
content_type 'application/json', charset: 'utf-8'
|
||||||
headers 'Access-Control-Allow-Origin' => '*'
|
headers 'Access-Control-Allow-Origin' => '*'
|
||||||
|
|
|
||||||
21
lib/bingo.rb
21
lib/bingo.rb
|
|
@ -86,7 +86,12 @@ module Bingo
|
||||||
status_cols: { 'dc' => 'ЦОД', 'state' => 'Сейчас', 'hours' => '24 часа', 'rtt' => 'мс', 'targets' => 'адр.' },
|
status_cols: { 'dc' => 'ЦОД', 'state' => 'Сейчас', 'hours' => '24 часа', 'rtt' => 'мс', 'targets' => 'адр.' },
|
||||||
status_dc: 'Доступность сети', status_hour: '%{h} ч назад: %{p}',
|
status_dc: 'Доступность сети', status_hour: '%{h} ч назад: %{p}',
|
||||||
status_desc: 'Доступность крупнейших дата-центров России онлайн: ежеминутные сетевые проверки IXcellerate, Selectel, DataPro, Key Point и других, история за 24 часа и открытые данные.',
|
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' => {
|
'en' => {
|
||||||
seo_title: 'Datacenter Bingo — strikes, fires and outages at Russian datacenters',
|
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_cols: { 'dc' => 'Datacenter', 'state' => 'Now', 'hours' => '24 hours', 'rtt' => 'ms', 'targets' => 'addr.' },
|
||||||
status_dc: 'Network reachability', status_hour: '%{h} h ago: %{p}',
|
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_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' => {
|
'uk' => {
|
||||||
seo_title: 'Бінго ЦОДів — атаки, пожежі та аварії дата-центрів Росії',
|
seo_title: 'Бінго ЦОДів — атаки, пожежі та аварії дата-центрів Росії',
|
||||||
|
|
@ -178,7 +188,12 @@ module Bingo
|
||||||
status_cols: { 'dc' => 'ЦОД', 'state' => 'Зараз', 'hours' => '24 години', 'rtt' => 'мс', 'targets' => 'адр.' },
|
status_cols: { 'dc' => 'ЦОД', 'state' => 'Зараз', 'hours' => '24 години', 'rtt' => 'мс', 'targets' => 'адр.' },
|
||||||
status_dc: 'Доступність мережі', status_hour: '%{h} год тому: %{p}',
|
status_dc: 'Доступність мережі', status_hour: '%{h} год тому: %{p}',
|
||||||
status_desc: 'Доступність найбільших дата-центрів Росії онлайн: щохвилинні мережеві перевірки IXcellerate, Selectel, DataPro, Key Point та інших, історія за 24 години та відкриті дані.',
|
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
|
}.freeze
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,17 @@ class BingoTest < Minitest::Test
|
||||||
assert_includes last_response.body, 'hackyourmom</a>'
|
assert_includes last_response.body, 'hackyourmom</a>'
|
||||||
end
|
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, '<a href="https://asxp.io/contact">asxp.io/contact</a>'
|
||||||
|
end
|
||||||
|
get '/en'
|
||||||
|
assert_includes last_response.body, '<a href="/en/policy">No-spam policy</a>'
|
||||||
|
end
|
||||||
|
|
||||||
def test_robots_and_sitemap_are_the_bingos_own
|
def test_robots_and_sitemap_are_the_bingos_own
|
||||||
get '/robots.txt'
|
get '/robots.txt'
|
||||||
assert last_response.ok?
|
assert last_response.ok?
|
||||||
|
|
@ -161,7 +172,8 @@ class BingoTest < Minitest::Test
|
||||||
%w[ru en uk].each { |l| assert_includes last_response.body, "<loc>https://example.org/#{l}/dc/dc-25</loc>" }
|
%w[ru en uk].each { |l| assert_includes last_response.body, "<loc>https://example.org/#{l}/dc/dc-25</loc>" }
|
||||||
assert_includes last_response.body, '<xhtml:link rel="alternate" hreflang="uk" href="https://example.org/uk/dc/dc-7"/>'
|
assert_includes last_response.body, '<xhtml:link rel="alternate" hreflang="uk" href="https://example.org/uk/dc/dc-7"/>'
|
||||||
%w[ru en uk].each { |l| assert_includes last_response.body, "<loc>https://example.org/#{l}/status</loc>" }
|
%w[ru en uk].each { |l| assert_includes last_response.body, "<loc>https://example.org/#{l}/status</loc>" }
|
||||||
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, "<loc>https://example.org/#{l}/policy</loc>" }
|
||||||
|
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"'
|
assert_includes last_response.body, 'hreflang="x-default"'
|
||||||
refute_includes last_response.body, 'changefreq'
|
refute_includes last_response.body, 'changefreq'
|
||||||
assert_match %r{</loc>\n <lastmod>\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ</lastmod>\n <xhtml:link}, last_response.body
|
assert_match %r{</loc>\n <lastmod>\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ</lastmod>\n <xhtml:link}, last_response.body
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
<p><%= t(:footer) %></p>
|
<p><%= t(:footer) %></p>
|
||||||
<p class="credits"><%= t(:credits_ai) %>
|
<p class="credits"><%= t(:credits_ai) %>
|
||||||
<%== format(Rack::Utils.escape_html(t(:credits_hym)), link: '<a href="https://hackyourmom.com/en/karta-data-czentriv-na-rasii/" target="_blank" rel="noopener">hackyourmom</a>') %></p>
|
<%== format(Rack::Utils.escape_html(t(:credits_hym)), link: '<a href="https://hackyourmom.com/en/karta-data-czentriv-na-rasii/" target="_blank" rel="noopener">hackyourmom</a>') %></p>
|
||||||
<p><a href="/<%= @locale %>/feed.xml">RSS</a> · <a href="/<%= @locale %>/feed.json">JSON Feed</a> · <a href="/data.json"><%= t(:open_data) %></a> · <a href="/<%= @locale %>/status"><%= t(:status_link) %></a></p>
|
<p><a href="/<%= @locale %>/feed.xml">RSS</a> · <a href="/<%= @locale %>/feed.json">JSON Feed</a> · <a href="/data.json"><%= t(:open_data) %></a> · <a href="/<%= @locale %>/status"><%= t(:status_link) %></a> · <a href="/<%= @locale %>/policy"><%= t(:policy_link) %></a></p>
|
||||||
<p>© <%= Date.today.year %> <a href="https://asxp.io">asxp.io</a> · <a href="<%= BingoWeb::SOURCE_URL %>" rel="noopener"><%= t(:source_code) %></a></p>
|
<p>© <%= Date.today.year %> <a href="https://asxp.io">asxp.io</a> · <a href="<%= BingoWeb::SOURCE_URL %>" rel="noopener"><%= t(:source_code) %></a></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
17
views/policy.erb
Normal file
17
views/policy.erb
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
<h1><%= t(:policy_link) %></h1>
|
||||||
|
<nav class="langs">
|
||||||
|
<% Bingo::LOCALES.each do |l| %>
|
||||||
|
<a href="<%= @path_for.(l) %>" hreflang="<%= l %>" lang="<%= l %>" <%== 'aria-current="page"' if l == @locale %>><%= l %></a>
|
||||||
|
<% end %>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<nav class="crumbs"><a href="/<%= @locale %>"><%= t(:back) %></a></nav>
|
||||||
|
|
||||||
|
<% t(:policy_text).each do |para| %>
|
||||||
|
<p class="lead"><%== format(Rack::Utils.escape_html(para), link: '<a href="https://asxp.io/contact">asxp.io/contact</a>') %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%== erb :_footer, layout: false %>
|
||||||
|
</main>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue