Commit graph

144 commits

Author SHA1 Message Date
27ee0e2aa8 deps: Update easingthemes/ssh-deploy action to v6
All checks were successful
Tests / test (push) Successful in 22s
2026-11-12 22:21:32 +00:00
e6cfcc6007
Merge Renovate: deps: Update dependency puma to v8
All checks were successful
Tests / test (push) Successful in 41s
Build and Deploy to Production / test (push) Successful in 22s
Build and Deploy to Production / build (push) Successful in 59s
Build and Deploy to Production / deploy (push) Successful in 39s
2026-11-13 02:09:23 +04:00
02097e3af7
Merge Renovate: deps: Update actions/checkout action to v7 2026-11-13 02:09:23 +04:00
8d1ef17314
Merge Renovate: deps: Update actions/cache action to v6 2026-11-13 02:09:22 +04:00
01be8a1b2f
Merge Renovate: deps: Update dependency sequel to v5.110.0 2026-11-13 02:09:22 +04:00
f8c28f4969
Merge Renovate: deps: Update dependency rack-test to v2.3.0 2026-11-13 02:09:22 +04:00
6ad17b5bee
Merge Renovate: deps: Update dependency pg to v1.7.0 2026-11-13 02:09:22 +04:00
bcc7aa1eb9
Merge Renovate: deps: Update dependency aws-sdk-s3 to v1.242.0 2026-11-13 02:09:21 +04:00
02eb66ae82
Merge Renovate: deps: Update dependency mail to v2.9.1 2026-11-13 02:09:21 +04:00
cc3da0af9c
Merge Renovate: deps: Update CI actions 2026-11-13 02:09:21 +04:00
6c998fc11d
ci: rebuild with the new registry token
All checks were successful
Build and Deploy to Production / test (push) Successful in 21s
Build and Deploy to Production / build (push) Successful in 59s
Build and Deploy to Production / deploy (push) Successful in 39s
The previous run failed to push its image: the token behind FJ_TOKEN
had been regenerated, and the secret now holds the new one.

Assisted-by: Claude:opus-5-5
2026-11-13 01:55:10 +04:00
3ab8ebcf3a
ci: stop using FORGEJO_* secret names
Some checks failed
Build and Deploy to Production / test (push) Successful in 22s
Build and Deploy to Production / build (push) Failing after 1s
Build and Deploy to Production / deploy (push) Has been skipped
Forgejo reserves names starting with FORGEJO_, and since runner v13
secrets.FORGEJO_TOKEN, _USER and _REGISTRY no longer reach the job as
the values set on the repository: Kaniko logged in with the wrong
credentials and every image push failed with UNAUTHORIZED.

The registry host and user are not secret (they are in every image
name), so write them in the workflow; the token comes from FJ_TOKEN,
as in datacenterbingo.

Assisted-by: Claude:opus-5-5
2026-11-13 01:47:28 +04:00
a2c89a0640 deps: Update dependency puma to v8
All checks were successful
Tests / test (push) Successful in 40s
2026-11-12 21:47:00 +00:00
b6e48cb69c deps: Update actions/checkout action to v7
All checks were successful
Tests / test (push) Successful in 22s
2026-11-12 21:46:43 +00:00
280c961b25 deps: Update actions/cache action to v6
All checks were successful
Tests / test (push) Successful in 24s
2026-11-12 21:46:40 +00:00
26db8d62d1 deps: Update dependency sequel to v5.110.0
All checks were successful
Tests / test (push) Successful in 41s
2026-11-12 21:46:32 +00:00
918c769953 deps: Update dependency rack-test to v2.3.0
All checks were successful
Tests / test (push) Successful in 40s
2026-11-12 21:46:24 +00:00
006f4f794f deps: Update dependency pg to v1.7.0
All checks were successful
Tests / test (push) Successful in 41s
2026-11-12 21:46:15 +00:00
e7bf6612be deps: Update dependency aws-sdk-s3 to v1.242.0
All checks were successful
Tests / test (push) Successful in 42s
2026-11-12 21:45:40 +00:00
f3f6f1f28f deps: Update dependency mail to v2.9.1
All checks were successful
Tests / test (push) Successful in 42s
2026-11-12 21:45:32 +00:00
98ceda5648 deps: Update CI actions
All checks were successful
Tests / test (push) Successful in 22s
2026-11-12 21:45:17 +00:00
9600e49757 Merge pull request 'chore: Configure Renovate' (#2) from renovate/configure into main
Some checks failed
Build and Deploy to Production / test (push) Successful in 22s
Build and Deploy to Production / build (push) Failing after 1s
Build and Deploy to Production / deploy (push) Has been skipped
Reviewed-on: #2
2026-11-12 20:35:02 +00:00
6fa65d5e0c deps: add renovate.json
All checks were successful
Tests / test (push) Successful in 1m5s
2026-11-12 20:32:38 +00:00
db8a9e207d
deps: update rack to 3.2.8
All checks were successful
Build and Deploy to Production / test (push) Successful in 45s
Build and Deploy to Production / build (push) Successful in 1m6s
Build and Deploy to Production / deploy (push) Successful in 40s
rack 3.2.6 has a denial of service in the multipart parser
(CVE-2026-125111, GHSA-5v64-555j-wjxm): parsed data stays in the scan
buffer, so a crafted upload makes memory grow without bound. The app
is reachable from the internet and accepts multipart forms.

Only the lock changes; the Gemfile already allows ~> 3.2.

Assisted-by: Claude:fable-5-2
2026-11-12 06:07:54 +04:00
4c10221727
admin: publish files to the public bucket
All checks were successful
Build and Deploy to Production / test (push) Successful in 27s
Build and Deploy to Production / build (push) Successful in 1m3s
Build and Deploy to Production / deploy (push) Successful in 40s
There was no way to put a file up for public use short of running an
S3 client with the uploader key. Add a "files" area to the admin:
upload one or more files, copy the link, edit the note, replace the
contents under the same link, delete. It has its own read/write/delete
permission flags like the other areas, and every action is audited.

The objects go to the storage stack's public bucket and are served by
Garage at s3.asxp.io/public/<key>, not by this app. The app's key can
only put and delete there, so a public_files table is the record of
what was published. Keys get a random prefix: links are not guessable
and names never collide.

That host is same-site with the admin, so a file that can carry script
must not render there. Only a fixed list of types (images, PDF, plain
text, audio, video) is served inline; the rest, HTML and SVG included,
is stored as an attachment. Image metadata is stripped by default, as
for the bingo photos, because of GPS tags.

A per-file limit and a total quota keep the small disk on asxpio-ops
from filling up.

Assisted-by: Claude:fable-5-2
2026-11-12 05:52:10 +04:00
f5d0ce9b48
docs: object storage is Garage now
All checks were successful
Build and Deploy to Production / test (push) Successful in 27s
Build and Deploy to Production / build (push) Successful in 1m10s
Build and Deploy to Production / deploy (push) Successful in 39s
CLAUDE.md and SECRETS.md still described MinIO: the endpoint, the
bootstrap script and the kind of key. Describe Garage, and say how the
deploy turns the ASXPIO_S3_* secrets into a Garage key.

Assisted-by: Claude:fable-5-2
2026-11-11 04:28:35 +04:00
dd31c20cc7
deploy: use Garage for object storage
All checks were successful
Build and Deploy to Production / test (push) Successful in 1m59s
Build and Deploy to Production / build (push) Successful in 1m15s
Build and Deploy to Production / deploy (push) Successful in 38s
The storage stack replaces MinIO, abandoned upstream, with Garage. Point
the app at garage:3900.

Garage only accepts keys in its own format, so hash the existing
ASXPIO_S3_* secrets into it the way storage's bootstrap does. The
secrets stay as they are; a pair already in Garage's format is used
unchanged.

Assisted-by: Claude:fable-5-2
2026-11-11 03:44:37 +04:00
9d88b8af8f
invoices: let the admin set the payment date
All checks were successful
Build and Deploy to Production / test (push) Successful in 1m40s
Build and Deploy to Production / build (push) Successful in 1m19s
Build and Deploy to Production / deploy (push) Successful in 56s
Payments often arrive before they are recorded, so stamping paid_at
with the click time skews received-per-month and days-to-pay stats.
Mark paid now takes an optional date, and a paid invoice gets a form
to correct it. Past days are stored at 12:00 UTC so the date holds in
any timezone; future and malformed dates are refused. The PDF does not
print the date, so nothing is re-rendered.

Assisted-by: Claude:opus-5-5
Claude-Session: https://claude.ai/code/session_01TCc5TvWnAs5xPvGMQXTJw3
2026-11-03 01:47:03 +04:00
9a802106ce
admin: send /admin/ to /admin, also after login
All checks were successful
Build and Deploy to Production / test (push) Successful in 26s
Build and Deploy to Production / build (push) Successful in 1m1s
Build and Deploy to Production / deploy (push) Successful in 36s
Opening /admin/ logged out stored next=/admin/ in the login form, and a
successful login then redirected to /admin/, which has no route (404).

Redirect trailing-slash admin URLs to the slashless route with a 301,
and drop trailing slashes from the post-login target too.

Assisted-by: Claude:opus-5-5
2026-10-18 10:39:10 +04:00
648effc21f
docker: ship bin/monitor in the image
All checks were successful
Build and Deploy to Production / test (push) Successful in 25s
Build and Deploy to Production / build (push) Successful in 1m2s
Build and Deploy to Production / deploy (push) Successful in 35s
.dockerignore left out all of bin/, which only held bin/test until now.
The asxpio-monitor container runs bin/monitor from the image and
crash-looped with "No such file or directory -- bin/monitor". Exclude
just bin/test.

Assisted-by: Claude:opus-5-5
2026-10-18 10:36:01 +04:00
0cc3e1dece
bingo: add a reachability monitor for the status page
All checks were successful
Build and Deploy to Production / test (push) Successful in 26s
Build and Deploy to Production / build (push) Successful in 1m11s
Build and Deploy to Production / deploy (push) Successful in 37s
datacenterbingo gets a public page showing whether each datacenter's
network answers. The probing lives here, next to the schema and the
admin, since the public site only has a read-only role.

bin/monitor runs as a second container from the same image. Once a
minute it opens one TCP connection to every active target (a handshake
or a refusal both count as answered) and folds the results into
5-minute rows per datacenter, plus a per-bucket row for control hosts in
Russia, so a lost vantage point isn't mistaken for dark sites. Rows are
kept 30 days. Targets are managed at /admin/bingo/monitor; only hosts a
public source ties to that one site belong there.

Migration 017 also grants the new tables to the bingo role, so the
public site can read them without a storage redeploy.

Assisted-by: Claude:opus-5-5
2026-10-18 10:31:00 +04:00
e0038ef3a4
Rerun CI after the runner's nix image was pinned
All checks were successful
Build and Deploy to Production / test (push) Successful in 49s
Build and Deploy to Production / build (push) Successful in 1m9s
Build and Deploy to Production / deploy (push) Successful in 36s
The previous runs died on the runner, not in the code: its nix-store
volume no longer matched nixos/nix:latest. The runner now uses
nixos/nix:2.35.3 with a volume seeded from that image.

Assisted-by: Claude:opus-5-5
2026-10-18 09:40:44 +04:00
642bec845a
ci: set nix options through NIX_CONFIG
Some checks failed
Build and Deploy to Production / test (push) Failing after 15s
Build and Deploy to Production / build (push) Has been skipped
Build and Deploy to Production / deploy (push) Has been skipped
The runner mounts a persistent nix-store volume over /nix. The current
nixos/nix image makes /etc/nix/nix.conf a symlink into /nix/store, and
the volume does not hold that path, so the link dangles and appending
to it fails before checkout: "/etc/nix/nix.conf: No such file or
directory".

Pass the settings in NIX_CONFIG instead. Without a config file Nix also
falls back to build-users-group = nixbld, which the container lacks, so
set it empty and turn the sandbox off, as the image config does.

Assisted-by: Claude:opus-5-5
2026-10-18 09:34:44 +04:00
9ea965190a
bingo: add VK to the providers
Some checks failed
Build and Deploy to Production / test (push) Failing after 1m7s
Build and Deploy to Production / build (push) Has been skipped
Build and Deploy to Production / deploy (push) Has been skipped
VK's own datacenter ИЦВА in Novosergiyevka is joining the card after
the drone strike of 18 October. Give it a provider slug so the filter
can show it, and guess it from the VK/ВКонтакте/М100 operator names.

Assisted-by: Claude:opus-5-5
2026-10-18 09:31:27 +04:00
f1ceb4efc7
bingo: store photos in their own bucket
All checks were successful
Build and Deploy to Production / test (push) Successful in 23s
Build and Deploy to Production / build (push) Successful in 1m8s
Build and Deploy to Production / deploy (push) Successful in 37s
The bingo photos sat under bingo/ in the invoice bucket. Store them in
BINGO_S3_BUCKET (datacenterbingo-photos in prod, created and pre-filled
by the storage repo with the same keys) and fall back to S3_BUCKET when
unset, so dev and tests need nothing new.

Assisted-by: Claude:opus-5
2026-10-12 04:09:08 +04:00
849980042e
bingo: move the public site to the datacenterbingo repo
All checks were successful
Build and Deploy to Production / test (push) Successful in 25s
Build and Deploy to Production / build (push) Successful in 1m2s
Build and Deploy to Production / deploy (push) Successful in 38s
The public card now runs from its own repo and container, with a
Traefik router of its own. Drop BingoWeb, its views, icons, Host
dispatch and the asxpio-bingo router here; keep the admin, the schema
and the photo pipeline, which the new site reads through a read-only
role.

BINGO_HOST stays for IndexNow pings and the admin's site link.

Assisted-by: Claude:opus-5
2026-10-12 03:52:30 +04:00
510998c0ed
bingo: credit Claude and hackyourmom in the footer
All checks were successful
Build and Deploy to Production / test (push) Successful in 28s
Build and Deploy to Production / build (push) Successful in 1m2s
Build and Deploy to Production / deploy (push) Successful in 38s
Part of the list was collected and verified with Claude, and several
entries came from the hackyourmom data-center map (Cat Eyes OSINT data).
Say so in the footer of the card and data-center pages, in all three
languages, and note that each entry was re-verified against operator
sites and the press.

Assisted-by: Claude:opus-5
2026-10-12 02:13:57 +04:00
79d28cae9e
bingo: more providers for the filter
All checks were successful
Build and Deploy to Production / test (push) Successful in 27s
Build and Deploy to Production / build (push) Successful in 1m2s
Build and Deploy to Production / deploy (push) Successful in 39s
Add DataSpace, Cloud.ru, X5 Group, КРОК, Stack Group and Beeline to the
provider list (with operator patterns for the automatic guess), ahead of
adding their verified sites.

Assisted-by: Claude:opus-5
2026-10-11 21:19:58 +04:00
c64098813b
bingo: build the card from chosen providers
All checks were successful
Build and Deploy to Production / test (push) Successful in 27s
Build and Deploy to Production / build (push) Successful in 1m1s
Build and Deploy to Production / deploy (push) Successful in 38s
Let visitors tick providers (Yandex, IXcellerate, RTK-DC, Sber, …) and
get the usual 5×5 card made of only those providers' datacenters, first
25 in ranking order, with lines scored as normal. A selection with fewer
than 25 sites leaves the rest of the card empty, so the card is always
5×5. Each provider chip shows crossed/total for the current view.

Datacenters get a provider slug, backfilled from the free-text operator,
guessed for new rows and editable in the admin. Filtered URLs are
noindex with canonical to the plain card.

Assisted-by: Claude:opus-5
2026-10-11 21:12:47 +04:00
9ba108b6b0
bingo: default to the operational card
All checks were successful
Build and Deploy to Production / test (push) Successful in 28s
Build and Deploy to Production / build (push) Successful in 1m8s
Build and Deploy to Production / deploy (push) Successful in 41s
The card led with construction projects (Sber Balakovo at #1, MOS7,
MOS11, Yandex Vladimir-2), so the first thing visitors saw was not what
actually runs today. Make the operational-only card the bare URL and
move the full list to ?view=all. The variant is noindex with canonical
to the bare page, old ?view=operational links still land on the default,
and the admin dashboard tile follows the public default.

Assisted-by: Claude:opus-5
2026-10-11 20:34:24 +04:00
a432a003a8
bingo: "63 MW (design)" in datacenter page prose
All checks were successful
Build and Deploy to Production / test (push) Successful in 26s
Build and Deploy to Production / build (push) Successful in 1m3s
Build and Deploy to Production / deploy (push) Successful in 39s
The facts sentence reused the dialog's compact label, so it read
"Capacity: 63 MW · design." Put the capacity type in parentheses
when it appears in prose.

Assisted-by: Claude:opus-5
2026-10-10 09:45:49 +04:00
f7ed3831f5
CLAUDE.md: document datacenter pages and IndexNow
All checks were successful
Build and Deploy to Production / test (push) Successful in 26s
Build and Deploy to Production / build (push) Successful in 1m2s
Build and Deploy to Production / deploy (push) Successful in 38s
Assisted-by: Claude:opus-5
2026-10-10 09:42:47 +04:00
26e862ff04
bingo: ping IndexNow when the card changes
A newly recorded incident only showed up in search once a crawler
happened to come back. IndexNow lets Bing, Yandex and the other
participating engines know about changed URLs right away.

Bingo::IndexNow posts the card URLs plus the affected datacenter pages
to api.indexnow.org, in a background thread so admin saves don't wait.
Incidents ping their datacenter. Edits ping the datacenter, or every
page when positions move (create, delete, reorder, sort). A deleted
datacenter's pages are included so the engines see the 404. BingoWeb
serves the key at /<key>.txt.

The key comes from the INDEXNOW Forgejo secret, written to .env as
INDEXNOW_KEY. With no key set this does nothing.

Assisted-by: Claude:opus-5
2026-10-10 09:42:46 +04:00
ca48f4292b
bingo: optional prose per datacenter page
The datacenter pages carry only generated sentences, which is thin
content to rank on. Migration 015 adds about_ru/en/uk, edited in the
admin form and rendered as paragraphs below the facts.

There is deliberately no fallback to ru: Russian prose on the English
page would hurt more than an absent paragraph.

Assisted-by: Claude:opus-5
2026-10-10 09:41:11 +04:00
feac965d02
bingo: a page per datacenter
Everything lived on one URL per language, with each datacenter's
details and incidents inside a <dialog>. That page can rank for the
brand and a couple of head terms, but not for searches like
"Yandex Sasovo drone strike".

Add /:locale/dc/:slug for every datacenter, reserve included. Each page
has its own title (naming the latest incident when there is one), a
description built from the facts, canonical and hreflang links, the
photo as og:image, and JSON-LD Place + BreadcrumbList. It shows the
facts in a sentence or two, the incident list with sources, and
prev/next links by position. The slug never changes, so the URLs are
stable.

The card cells are now links to these pages. A plain click still opens
the dialog, while modified clicks open the page. The dialog and the
incident log link to the page too, and the sitemap lists every page in
all three languages. The layout takes title, description, alternates
and JSON-LD from the route, and the dialog and the page share the meta
and incident partials.

Assisted-by: Claude:opus-5
2026-10-10 09:40:16 +04:00
1646caec1b
CLAUDE.md: document the bingo SEO changes
All checks were successful
Build and Deploy to Production / test (push) Successful in 26s
Build and Deploy to Production / build (push) Successful in 1m1s
Build and Deploy to Production / deploy (push) Successful in 38s
Assisted-by: Claude:opus-5
2026-10-10 09:31:25 +04:00
29bd2df28a
bingo: send HSTS
The edge already redirects http:// to https://, but without HSTS the
first request of every visit can still go out in the clear. Turn on
Rack::Protection's StrictTransport for the bingo app: one year, this
host only, no preload.

Assisted-by: Claude:opus-5
2026-10-10 09:31:25 +04:00
6f37e537ca
bingo: lastmod from the last edit, not the last insert
The "updated" date and the sitemap lastmod came from max(created_at),
so editing a datacenter, fixing an incident title or deleting an
incident left them stale. Google stops trusting lastmod once it is
seen to be wrong.

Migration 014 adds updated_at (backfilled from created_at). Saves set
it, renumbering bumps only the rows whose position actually moved, and
deleting an incident bumps its datacenter. The admin delete now goes
through the model so the hook runs. The sitemap gives lastmod as a
full timestamp and drops changefreq, which Google ignores.

Assisted-by: Claude:opus-5
2026-10-10 09:30:37 +04:00
b100b6a8cd
bingo: fix the JSON-LD place markup
Each Place carried its info_url as sameAs, but sameAs means "this is
the same entity", and the URL is an operator page or news article
about the datacenter. Use subjectOf with a CreativeWork instead.

Incidents were missing from the structured data entirely. Add them as
Events at the place (name, start date, source URL), and give the
ItemList a dateModified.

Assisted-by: Claude:opus-5
2026-10-10 09:29:35 +04:00
4e074fd4bf
bingo: 1200x630 JPEG for og:image
og:image pointed at the 1200px WebP dialog variant: no declared size,
a 16:9 frame that link previews crop unpredictably, and WebP isn't
rendered by every preview client. The alt text was just the site name.

Photo variants now include <slug>-<hex>-og.jpg, a centre crop to
1200x630, and the head declares its type and size. Migration 013 adds
photo_og so rows built before this keep using the WebP until "Rebuild
resized photos" is run. The alt text names the pictured datacenter and
city. resize and the new og_crop share one convert call.

Assisted-by: Claude:opus-5
2026-10-10 09:28:57 +04:00