The companion SHA tag step failed on the last build even though the image had
already been pushed and deployed. Harbor's vulnerability policy blocks manifest
reads while Trivy's scan is Running, and `imagetools create` copies the manifest,
so Harbor answered 412 Precondition Failed.
Two faults compounded. The loop gated on `imagetools inspect`, but a probe passing
is no guarantee the copy will be allowed. And `create` sat outside the condition,
so under the default `bash -e` its first failure aborted the step before `exit 0`
or any further attempt: the log shows one waiting line, then the error, with 58 of
60 retries unused.
Retry the real operation instead, inside the condition so a blocked copy is a
reason to wait rather than fatal. The trade-off is that a permanently failing copy
now takes the full ten minutes to report instead of failing fast; the previous
behaviour failed fast but wrongly, on a transient block.
Also records that only the primary tag matters for rollout, since the Flux image
policy matches the timestamp tag, so a failure in this step costs the audit tag
rather than the deployment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
htmx's WebSocket extension reconnects only after close codes 1006/1011/1012/1013.
_ws_proxy closed the browser socket with a normal 1000 on every upstream exit, so
htmx read a btcdata restart as a deliberate shutdown and left both panels frozen
until the visitor reloaded. New browser connections always worked, which hid the
defect from non-browser checks.
The proxy now reconnects upstream behind the still-open browser socket on a bounded
schedule, resets that schedule after a connection that lasted long enough to count
as healthy, and closes with 1012 only once the schedule runs out, so recovery falls
back to htmx's own backoff. A concurrent reader detects a departed browser, which a
dead upstream would otherwise be indistinguishable from, and an unrenderable payload
is logged and skipped rather than ending the stream.
tests/test_ws_proxy.py covers this against a real loopback WebSocket server: same
socket recovery across an upstream restart for both streams, the retryable close
code when the upstream stays down, and skip-on-unrenderable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
omarchy is gone, but the citadel deployment still set
BTCDATA_URL=http://192.168.50.42:4102 and ran standalone on its own
compose network. Every request that proxies to btcdata failed with
httpx.ConnectError: All connection attempts failed.
btcdata now runs on citadel too, so attach btcprice to the external
btc-network there and reach it by service name at http://btcdata:4102.
deploy-citadel.sh creates the network on the remote if missing, as
btcdata's own deploy script already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRkiLhK5qhkZjP2adxpA3e
Port of ../elprice/deploy-citadel.sh: build the image locally for the
remote's architecture, ship it over SSH with docker save | docker load,
and run docker compose up -d --no-build in /home/pmp/apps/btcprice.
The deployment compose is standalone rather than joining btc-network,
because citadel runs no btcdata — BTCDATA_URL points at the instance
already running on omarchy (http://192.168.50.42:4102).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdMTk61JgUdNH86ttMi4ce
Replace the Clojure Kit application with a Python FastAPI app that
produces identical HTML output. WebSocket proxy, Danish number
formatting, and HTMX integration all preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sats value now comes from btcdata's Strike quote endpoint which
includes the actual fee/spread, matching the Strike app.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show 54€ equivalent in sats with dark blue styling, light gray background,
and Danish number formatting for BTC/USDT and BTC/EUR values.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Receive raw JSON from btcdata, transform to HTML in ws_proxy.clj.
Added data.json dependency, price and strike rendering, safelist updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove btcdata compose include, use external shared network instead
so both projects can run as separate Docker stacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browser can't reach the LAN-only btcdata WebSocket when accessing
btcprice remotely (Tailscale/Cloudflare Tunnel). Add a /ws/price
proxy route that relays btcdata frames to the browser so only
btcprice needs to be reachable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Safelist Tailwind classes for currency rate HTML fragments from btcdata
WebSocket. Add make run-docker target that connects to Docker btcdata
on port 4101.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace vanilla JS WebSocket with HTMX ws extension (hx-ext="ws")
- Add HTMX + ws extension CDN scripts to page head
- Zero client-side JavaScript — HTMX handles connection, reconnect, and
DOM swap of HTML fragments from btcdata
- Hidden div safelists Tailwind classes used in btcdata HTML fragments
- Update BTCDATA_URL to LAN IP (192.168.50.42) for cross-machine access
- Update CLAUDE.md and README.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch from SSE EventSource to WebSocket client connecting to
btcdata's new /api/price/ws endpoint
- Add automatic reconnect on connection loss (3s delay)
- Update Dockerfile BTCDATA_URL to use btcdata's new Docker port 4101
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Docker runs on port 4041 alongside local dev on 4000.
Include btcdata docker-compose for unified startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove DB, Binance WebSocket, and Kraken pollers from btcprice.
UI now uses vanilla EventSource connecting to btcdata for live
price updates. btcprice is now a pure UI service with no database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Polls Kraken API for 1-day BTC/USD candles aligned to 00:01 UTC,
mirroring the existing hourly poller pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hourly candles only finalize on the hour, so polling every 5 minutes
was wasteful. Now the poller sleeps until 1 minute past each hour,
fetches immediately on first run when no data exists, and logs time
to next fetch on startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetches Bitcoin hourly candles from the Kraken public REST API
and upserts them into a kraken_hour table on a 5-minute poll interval,
following the existing Integrant component pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch to available base images (temurin-21) since clojure:openjdk-25
and azul/zulu-openjdk-alpine:25 no longer exist. Add docker-compose.yml
for single-command app deployment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Node.js stage for CSS build, cache Clojure deps in separate layer,
add .dockerignore, and default JDBC_URL to btcprod database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>