Each IP may now make 120 requests per minute instead of 60. Under
load the limit drops to 60 RPM once the site reaches 600 RPS and to
30 RPM at 1000 RPS, instead of 30 and 15 RPM at 200 and 300 RPS.
One page load makes three or four requests, and many visitors share
an IP behind carrier-grade NAT or an office gateway, so the old
limits throttled real users. The old global thresholds were also far
below what the host can serve. Update the FAQ in every locale to
match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fs.nr_open = 65536 sat below systemd's default hard NOFILE limit
(524288), so any service without its own LimitNOFILE failed to restart
once the sysctl was applied (205/LIMITS); fail2ban was down after the
last deploy, and after a reboot ssh could be too. Leave nr_open at the
kernel default.
fail2ban also read /var/log/auth.log, which Debian 13 no longer
writes; read sshd's journal instead.
Disable password and keyboard-interactive SSH logins and allow root
with keys only. The drop-in is named 10-* so it wins over cloud-init's
50-cloud-init.conf, which turns password logins on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The firewall role's "Reload systemd" handler shadowed the docker role's
handler of the same name, and its flush_handlers ran the pending Docker
restart right after base lowered fs.nr_open, without a daemon-reload.
Docker then started without its LimitNOFILE override and systemd could
not apply the default limit (status=205/LIMITS).
Reload systemd inline in the firewall role instead of through a
handler, so other roles' handlers keep running at the end of the play,
and reload before ensuring Docker is running so a host left in this
state recovers on the next deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Vultr firewall now only opens 443 (and SSH); the host decides who may
use it. Docker-published ports bypass UFW's INPUT rules, so the new
firewall role routes Docker traffic through UFW's route rules via the
DOCKER-USER chain and allows Cloudflare's IPv4 ranges on 443 with
`ufw route allow`. ufw-cloudflare-sync keeps those rules in step with
https://www.cloudflare.com/ips-v4 daily via a systemd timer, refusing
to touch the rules if the list looks wrong.
The role runs on every deploy, before HAProxy starts, and fails the
deploy if the filtering is not in place, since HAProxy trusts
CF-Connecting-IP from anything that reaches it. The plain UFW allows
for 80/443 are dropped from the base role as they never applied to
Docker-published ports.
Rotating the deploy SSH key now rebuilds the instance, because Vultr
only installs SSH keys at creation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The daily scheduled update at 01:20 UTC was systematically lagging the
upstream mergedipdb release by 24h. The upstream cron nominally fires
at 01:00 UTC but GitHub Actions schedule latency shifts the actual
publish time anywhere from ~01:05 to ~06:00+ UTC, so any fixed
single-daily time misses the current release on delayed days.
Introduce DB_UPDATE_INTERVAL_HOURS (default 6, must divide 24 evenly),
shift the anchor DB_UPDATE_TIME_UTC default to 00:20, and replace
secs_until_next with secs_until_next_tick that schedules at anchor +
N*interval. Also set RUST_LOG=rustyip=info,warn in the Ansible env
template so the scheduler and "database updated successfully (N bytes)"
info logs are actually visible in docker logs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wipe the target color's database volume before starting the container so
each push re-downloads the latest Merged-IP.mmdb. Gate /health on DB
readiness (503 until loaded) so HAProxy blue-green only flips traffic
once the new backend has a usable DB, and raise the Ansible health-wait
window from 120s to 360s to cover the download.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch from High Frequency 2vCPU/2GB ($18/mo) to Regular Performance
1vCPU/1GB ($5/mo). Tighten HAProxy rate limits from 100/50/30 RPM to
60/30/15 RPM with lower global RPS thresholds (200/300 instead of
300/500). Reduce maxconn, socket buffers, file descriptor limits, and
Docker resource settings to fit within 1GB RAM constraints. Update all
user-facing content and translations across 14 locales.
Replace DB_UPDATE_INTERVAL_HOURS with DB_UPDATE_TIME_UTC (HH:MM format,
default 01:20) so the database update runs at a predictable time each day.
This ensures the update happens 20 minutes after the upstream Merged IP
Data release at 01:00 UTC.
Docker file-level bind mounts track inodes, not paths. Ansible template
writes create new inodes, leaving the container reading orphaned files.
Replacing SIGHUP with force-recreate ensures fresh mounts every time,
eliminating the admin socket dependency entirely.