From 81227cb90f4e7d53e9e3579058d18ea59749b071 Mon Sep 17 00:00:00 2001 From: satoshi Date: Sun, 26 Apr 2026 23:10:36 +0300 Subject: [PATCH] Tighten OSS detection, refresh TLS badge, plumb custom mempool URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isOpenSource was matching things like Braiins OS, cgminer, bfgminer, and a generic "esp32" — software that's open but runs on closed hardware (Antminer, unknown rigs). public-pool's UI reserves the star for open-source HARDWARE, with open firmware on top, and that's the convention to match. New regex covers the family verbatim from public-pool-ui's user-agent-link switch table: bitaxe, bitaxeHex, NerdMiner, NerdNOS, NerdAxe, NerdAxeGamma, NerdOCTAXE, NerdEKO, NerdQAxe+, NerdQAxe++, PiAxe, QAxe, QAxe+, 0xAxe, LeafMiner Also extend detectHardware so all those user-agents get a recognised label instead of "esp32" / raw token. Replace the squished "TLS" pill with an inline-flex badge: 0.78em text, 5px corners, padlock SVG, generous left/right padding so it reads at a glance instead of looking like a typo. Custom block explorer: new optional union under StartOS Advanced config ("Block Explorer" — defaults to "mempool.space"). Picking "Custom URL" surfaces the value as MEMPOOL_BASE_URL on the container env. config.Config picks it up, the aggregator copies it into every Snapshot (mempool_base_url field), and the UI's shared explorerBaseFor() helper trusts the custom URL verbatim when present (no /testnet4 / /signet path appended — a self-hosted instance is presumably single-network already). Falls back to the public mempool.space mirrors per chain when unset, which is the default behaviour. --- api/cmd/kamado-api/main.go | 1 + api/internal/config/config.go | 6 +++++ api/internal/state/aggregator.go | 11 +++++++- ui/src/format.ts | 46 ++++++++++++++++++++++++++------ ui/src/lib/BlocksTable.svelte | 13 +++------ ui/src/lib/MinersTable.svelte | 33 ++++++++++++++++------- ui/src/lib/UserDetailPage.svelte | 42 ++++++++++++++++++----------- ui/src/types.ts | 3 +++ 8 files changed, 112 insertions(+), 43 deletions(-) diff --git a/api/cmd/kamado-api/main.go b/api/cmd/kamado-api/main.go index 49d8f83..46ac697 100644 --- a/api/cmd/kamado-api/main.go +++ b/api/cmd/kamado-api/main.go @@ -60,6 +60,7 @@ func main() { agg := state.New(ck, rpc, cfg.PollInterval, log) agg.Store = blockStore + agg.MempoolBaseURL = cfg.MempoolBaseURL ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() diff --git a/api/internal/config/config.go b/api/internal/config/config.go index f4f51c3..769049a 100644 --- a/api/internal/config/config.go +++ b/api/internal/config/config.go @@ -34,6 +34,11 @@ type Config struct { // Poll interval for refreshing ckpool stats PollInterval time.Duration + + // Optional mempool.space-compatible explorer base URL. Empty means + // the UI uses the public mempool.space; non-empty means the user + // has pointed Kamado at their own instance via the StartOS config. + MempoolBaseURL string } func FromEnv() (*Config, error) { @@ -48,6 +53,7 @@ func FromEnv() (*Config, error) { CKPoolLogFile: getenv("CKPOOL_LOGFILE", "/var/log/ckpool/ckpool.log"), DBPath: getenv("DB_PATH", "/var/lib/kamado/kamado.db"), PollInterval: getenvDuration("POLL_INTERVAL", 5*time.Second), + MempoolBaseURL: os.Getenv("MEMPOOL_BASE_URL"), } if cfg.BitcoinRPCURL == "" { diff --git a/api/internal/state/aggregator.go b/api/internal/state/aggregator.go index 5db4bbc..13e8550 100644 --- a/api/internal/state/aggregator.go +++ b/api/internal/state/aggregator.go @@ -68,6 +68,11 @@ type Snapshot struct { // 24-hour hashrate history, sampled once per minute (max 1440 points). HashrateHistory []HashratePoint `json:"hashrate_history,omitempty"` + // Optional override for explorer links in the UI. Empty means use + // mempool.space defaults. Set via MEMPOOL_BASE_URL env, surfaced by + // the StartOS config "Block Explorer" -> "Custom URL". + MempoolBaseURL string `json:"mempool_base_url,omitempty"` + // Health CKPoolOK bool `json:"ckpool_ok"` BitcoinOK bool `json:"bitcoin_ok"` @@ -93,6 +98,10 @@ type Aggregator struct { Interval time.Duration Log *slog.Logger + // Static config that the UI consumes via the snapshot. Empty + // MempoolBaseURL leaves the UI on its mempool.space defaults. + MempoolBaseURL string + // OnRefresh, if set, is called (non-blocking) after each snapshot // refresh. Used by the WebSocket hub to push updates to clients. OnRefresh func(Snapshot) @@ -187,7 +196,7 @@ func (a *Aggregator) refresh(ctx context.Context) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - next := Snapshot{GeneratedAt: time.Now()} + next := Snapshot{GeneratedAt: time.Now(), MempoolBaseURL: a.MempoolBaseURL} // --- ckpool: poolstats, users, workers, clients, uptime --- if ps, err := a.CK.PoolStats(ctx); err == nil { diff --git a/ui/src/format.ts b/ui/src/format.ts index 249d8f5..8b7d76f 100644 --- a/ui/src/format.ts +++ b/ui/src/format.ts @@ -63,14 +63,16 @@ export function formatAgo(unixSeconds: number): string { return `${Math.floor(ageSec / 86400)}d ago`; } -// Open-source ASIC hardware (Bitaxe family, NerdMiner, etc.) and -// open-source firmware projects (Braiins OS, cgminer/bfgminer/ -// ckminer). Used to render a star next to the hardware label, like -// public-pool does, so visitors can spot DIY / OSS rigs at a glance. +// Star = open-source HARDWARE *and* SOFTWARE, matching public-pool's +// classification at https://github.com/benjamin-wilson/public-pool-ui +// (user-agent-link.component.html). Closed hardware running open +// firmware (Braiins OS on Antminer) doesn't qualify, and neither do +// generic CPU/cgminer agents that don't tell us what hardware +// they're running on. export function isOpenSource(ua: string): boolean { if (!ua) return false; const s = ua.toLowerCase(); - return /bitaxe|nerdaxe|nerdqaxe|nerdoctaxe|nerdminer|qaxe|mccm|lucky|braiins|cgminer|bfgminer|ckminer|esp32/.test(s); + return /bitaxe|nerdminer|nerdnos|nerdaxe|nerdoctaxe|nerdeko|nerdqaxe|piaxe|qaxe|0xaxe|leafminer/.test(s); } // Extract the BTC payout address from a stratum username. Stratum @@ -82,6 +84,23 @@ export function btcAddressOf(workername: string): string { return dot < 0 ? workername : workername.slice(0, dot); } +// Build the explorer base URL for the active network. If the user +// configured a custom mempool instance via StartOS, we trust that +// verbatim and don't append a /testnet4 / /signet path — the user's +// instance presumably runs on a single network already. Otherwise +// fall back to mempool.space with the appropriate network prefix. +export function explorerBaseFor( + chain: string | undefined, + customBaseUrl: string | undefined, +): string { + if (customBaseUrl && customBaseUrl.length > 0) { + return customBaseUrl.replace(/\/+$/, ""); + } + if (chain === "test" || chain === "testnet4") return "https://mempool.space/testnet4"; + if (chain === "signet") return "https://mempool.space/signet"; + return "https://mempool.space"; +} + // Detect common Bitcoin mining hardware from the stratum useragent // string. This is a best-effort heuristic; unknown agents fall back // to a stripped version of the raw string. Order matters — check @@ -90,14 +109,23 @@ export function detectHardware(ua: string): string { if (!ua) return "unknown"; const s = ua.toLowerCase(); const rules: Array<[RegExp, string]> = [ + // Bitaxe and Nerd-family open-source ASICs. Order matters — check + // narrower variants before the broader Nerd* / Bitaxe roots. + [/bitaxehex/, "Bitaxe Hex"], [/bitaxe/, "Bitaxe"], + [/nerdqaxe\+\+/, "NerdQAxe++"], [/nerdqaxe/, "NerdQAxe+"], + [/nerdaxegamma/, "NerdAxeGamma"], [/nerdaxe/, "NerdAxe"], + [/nerdoctaxe/, "NerdOCTAXE"], + [/nerdeko/, "NerdEKO"], + [/nerdnos/, "NerdNOS"], [/nerdminer/, "NerdMiner"], - [/nerdoctaxe/, "NerdOctaxe"], - [/mccm/, "MCCM"], - [/lucky(?:miner)?/, "Lucky Miner"], + [/0xaxe/, "0xAxe"], + [/qaxe\+/, "QAxe+"], [/qaxe/, "QAxe"], + [/piaxe/, "PiAxe"], + [/leafminer/, "LeafMiner"], [/antminer/, "Antminer"], [/whatsminer/, "Whatsminer"], [/avalon/, "Avalon"], @@ -105,7 +133,9 @@ export function detectHardware(ua: string): string { [/bfgminer/, "bfgminer"], [/bmminer/, "BMMiner"], [/ckminer/, "ckminer"], + [/cpuminer/, "cpuminer"], [/braiins/, "Braiins OS"], + [/termux/, "termux-miner"], [/micro[- ]?bt/, "MicroBT"], [/esp32/, "ESP32"], [/s9/, "Antminer S9"], diff --git a/ui/src/lib/BlocksTable.svelte b/ui/src/lib/BlocksTable.svelte index 72c6ae7..13324eb 100644 --- a/ui/src/lib/BlocksTable.svelte +++ b/ui/src/lib/BlocksTable.svelte @@ -1,20 +1,15 @@
diff --git a/ui/src/lib/MinersTable.svelte b/ui/src/lib/MinersTable.svelte index 826aedb..451eb05 100644 --- a/ui/src/lib/MinersTable.svelte +++ b/ui/src/lib/MinersTable.svelte @@ -121,7 +121,12 @@
{r.workerName} {#if r.tls} - TLS + + + TLS + {/if}