Show domains in pool status

This commit is contained in:
2026-08-18 02:53:40 +03:00
parent a1fa90c66a
commit eb5f4cbf5a
12 changed files with 517 additions and 141 deletions
+3 -3
View File
@@ -22,7 +22,7 @@ This is the 0.4.0 port of the [0.3.5.1 wrapper](../KamadoPool-StartOS-0351), reb
Retained behavior: the ckpool restart loop gated on bitcoind reachability (now a daemon wrapper script), dual block-detection (ZMQ + 100 ms blockpoll), loopback stratum binds for TLS tagging (now one per certificate, declared to kamado-api via `STRATUM_SERVERS` so the dashboard names the certificate on hover), the v4 self-signed certificate with broad SANs, and ckpool loglevel 6 with `--log-shares`.
Stratum TLS serves **two** certificates on one port, selected per connection by SNI: a Let's Encrypt certificate for any clearnet domain attached to the Stratum (TLS) interface (fetched with `getSslCertificate`, no config field — the attached domains *are* the setting), and the self-signed certificate as the default for LAN miners, which send no SNI and fall through to it. The TLS interface binds unconditionally so a domain can be attached without first enabling local TLS.
Stratum is exposed on **three** endpoints, each a separate interface and port. Plaintext, and two TLS paths that differ in who terminates them: **Stratum (TLS, Public Domain)** declares `addSsl`, so StartOS terminates it and issues/renews a Let's Encrypt certificate for any domain attached to it — this is the only way to get a publicly trusted certificate, because StartOS provisions ACME only for endpoints it terminates TLS for, and a raw TCP binding is offered no certificate authority at all. **Stratum (TLS, Local Network)** stays raw TCP terminated in-container by stunnel with the persisted self-signed certificate, for LAN miners that pin it — public CAs cannot sign a bare IP or `.local` name. Each forwards to its own ckpool bind, so `serverurl` index identifies the path and the dashboard padlock names the certificate in use.
## Prerequisites
@@ -77,12 +77,12 @@ make KAMADO_SRC=/path/to/KamadoPool
startos/
manifest/ id, images (local Dockerfile build), volumes, bitcoind dependency
main.ts subcontainer, ckpool.conf + stunnel.conf rendering, daemons + health checks (fixed internal ports)
interfaces.ts Web UI (http 8080), Stratum (raw TCP, configurable), Stratum TLS (raw TCP, configurable; always bound so domains can attach)
interfaces.ts Web UI (http 8080), Stratum (raw TCP), Stratum TLS Local (raw TCP, stunnel/self-signed), Stratum TLS Public (addSsl, OS-terminated/Let's Encrypt)
fileModels/ store.json (service settings, incl. stratum ports)
actions/ Configure, Pool Status, Stratum TLS Certificate, Regenerate TLS Certificate, Reset Block Latency
dependencies.ts bitcoind (running, synced) + ZMQ autoconfig task
backups.ts volumes: main, ckpool
versions/ 0.2.0:3 with migration from the 0.3.5.1 wrapper
versions/ 0.2.2:0, graph back to 0.2.0 with migration from the 0.3.5.1 wrapper
init/ store seeding, init ordering
i18n/ dictionaries (en, es, de, pl, fr)
assets/
+31 -29
View File
@@ -7,7 +7,7 @@ Kamado is a solo Bitcoin mining pool built on a patched fork of CKPool-solo, wit
- **A running solo pool**: stratum server (ckpool), middleware API, and web dashboard, supervised as separate daemons with individual health checks.
- **A real-time dashboard** with live hashrate, per-miner stats, hardware detection, block history, best-share leaderboards, and a transaction accelerator.
- **Direct LAN stratum access**: StartOS 0.4.0 exposes the stratum TCP port on your network — no router port-forward or proxy needed (this was a 0.3.x limitation).
- **Stratum TLS on both sides of the network**: a publicly trusted certificate for miners connecting over a clearnet domain, and a persisted self-signed certificate for miners on the LAN — on the same port, at the same time.
- **Stratum TLS on both sides of the network**: a Let's Encrypt certificate issued by StartOS for miners connecting over a clearnet domain, and a persisted self-signed certificate for miners on the LAN — each on its own port, both alongside the plaintext endpoint.
## Setup
@@ -30,47 +30,44 @@ stratum+tcp://<your-server-lan-address>:<stratum-port>
### Stratum over TLS
Kamado exposes two stratum interfaces — **Stratum** (plaintext) and **Stratum (TLS)** — and each is reachable both on your local network and, if you attach a domain, over the internet. That gives four working combinations:
Kamado offers two separate TLS endpoints, because they are secured in different ways:
| Path | Endpoint | Certificate | Setup needed |
| ---- | -------- | ----------- | ------------ |
| Plaintext, local network | `stratum+tcp://<lan-address>:3333` | — | none |
| Plaintext, public domain | `stratum+tcp://<your-domain>:3333` | — | attach a domain to **Stratum** |
| TLS, local network | `stratum+ssl://<lan-address>:3334` | self-signed | enable **Stratum TLS (Local Network)** |
| TLS, public domain | `stratum+ssl://<your-domain>:3334` | Let's Encrypt | attach a domain to **Stratum (TLS)** |
**Over the internet — Stratum (TLS, Public Domain).** Attach a domain to this interface in the StartOS UI and choose Let's Encrypt. StartOS obtains and renews the certificate and terminates TLS itself, so miners validate it against their normal root store with nothing pasted in. See *Reaching the pool from the internet* below — attaching the domain is not quite the whole setup.
The dashboard shows a padlock next to every encrypted miner; hover it to see which of the two certificates that miner is actually using.
#### TLS over a public domain (no certificate setup)
Add a clearnet domain to the **Stratum (TLS)** interface in the StartOS interface list. StartOS obtains a Let's Encrypt certificate for it and Kamado starts serving it automatically — there is nothing to enable in *Configure* and nothing to paste into your miners. Any miner whose firmware ships a normal CA root store validates it the same way a browser validates a website.
Certificates renew automatically. Kamado picks up each renewal on its own, which briefly restarts the stratum listener — connected miners reconnect within seconds.
#### TLS on the local network (self-signed)
A Let's Encrypt certificate is only ever valid for the domain name it was issued for, so a miner pointed at a bare LAN IP cannot validate it. For those miners, enable **Stratum TLS (Local Network)** in *Configure*. Kamado generates a self-signed certificate once and persists it, so pinned fingerprints survive restarts and updates.
Run the **Stratum TLS Certificate** action to get:
**On your local network — Stratum (TLS, Local Network).** Enable **Stratum TLS (Local Network)** in *Configure* to serve a self-signed certificate on its own port. Public certificate authorities cannot sign a bare LAN IP or a `.local` name, so this path stays self-signed and miners must be told to trust it. Run the **Stratum TLS Certificate** action to get:
- the **SHA-256 fingerprint** for firmwares that pin fingerprints, and
- the **full PEM** to paste into firmwares that accept a custom root (AxeOS exposes a *Stratum SSL Cert* field for exactly this).
Otherwise connect with `stratum+ssl://` and certificate verification disabled. Use the **Regenerate TLS Certificate** action to rotate the certificate; miners that pin it will need the new fingerprint.
Otherwise connect with `stratum+ssl://` and certificate verification disabled. Use the **Regenerate TLS Certificate** action to rotate it; miners that pin it will need the new fingerprint.
#### How the two share one port
Both TLS endpoints and the plaintext one run at the same time on their own ports, and the dashboard's padlock names which certificate each miner arrived on when you hover it.
Both certificates are served on the same TLS port, selected per connection by the hostname the miner asks for (SNI). A miner that connects by domain name gets the Let's Encrypt certificate for that name; a miner that connects to a bare IP sends no hostname and gets the self-signed one. You do not have to choose, and you do not need a second port.
### Reaching the pool from the internet
If you would rather have your LAN miners use the publicly trusted certificate too, point your local DNS (router, Pi-hole, AdGuard) at the server's LAN address for your domain and connect them by domain name. They then validate against Let's Encrypt while their traffic stays on the LAN — and you can leave **Stratum TLS (Local Network)** off entirely.
Miners outside your network use the **Stratum (TLS, Public Domain)** interface:
1. **Attach a domain** to that interface and choose Let's Encrypt. Point its DNS at whatever address the internet reaches your server on.
2. **Point miners at the domain and its port.** A domain is published on the port set in *Configure* (Preferred Stratum TLS Port (Public Domain)), which can differ from the port shown for IP-based access on the same interface. The **Pool Status** action lists every endpoint and every attached domain with the port it is published on.
```text
stratum+ssl://<your-domain>:<public-tls-port>
```
Set the miner to use its **system/built-in certificates** — a Let's Encrypt chain validates against any normal root store with nothing pasted in.
Two things worth knowing if a certificate never appears:
- **Issuance validates on port 443 of your domain**, using the TLS-ALPN-01 challenge, regardless of which port the pool itself runs on. If your server isn't reachable on 443 for that name, issuance fails and StartOS falls back to a self-signed certificate that public clients reject.
- **A hostname that has failed validation repeatedly gets rate-limited** by Let's Encrypt (5 failures per hostname per hour), and StartOS retries roughly every minute — so a name that failed early can stay stuck long after the original cause is fixed. Check `start-cli server logs | grep -i acme`; if you see `ACME order failed` looping, remove the domain to stop the retries, wait for the hour to clear, then re-add it. Using a fresh hostname sidesteps it entirely.
## Configuration
Everything lives in the **Configure** action: the stratum and stratum-TLS ports, vardiff (starting/min/max difficulty), idle-client disconnect, the coinbase tag embedded in solved blocks, ZMQ, local-network TLS, log level, and an optional self-hosted mempool explorer URL for dashboard links.
Everything lives in the **Configure** action: the three stratum ports (plaintext, local-network TLS, public-domain TLS), vardiff (starting/min/max difficulty), idle-client disconnect, the coinbase tag embedded in solved blocks, ZMQ, local-network TLS, log level, and an optional self-hosted mempool explorer URL for dashboard links.
TLS over a public domain is deliberately *not* a config option — it follows whatever domains you attach to the **Stratum (TLS)** interface, so there is no second copy of that setting to drift out of sync with what the OS actually has.
Which domain is used for public TLS is deliberately *not* a config option — it follows whatever you attach to the **Stratum (TLS, Public Domain)** interface, so there is no second copy of that setting to drift out of sync with what the OS actually has. Only its port lives in *Configure*.
Changing a port rebinds the interface without restarting the pool, so miners already connected on other ports keep hashing — but anything pointed at the old port must be updated. Setting both stratum ports to the same number is rejected when you save.
Changing a port rebinds the interface without restarting the pool, so miners already connected on other ports keep hashing — but anything pointed at the old port must be updated. Setting any two of the three ports to the same number is rejected when you save.
## Actions
@@ -85,8 +82,13 @@ Changing a port rebinds the interface without restarting the pool, so miners alr
- **Bitcoin Core RPC errors**: make sure Bitcoin Core is running and fully synced; Kamado's *Bitcoin Core RPC* health check shows the current state.
- **Best share resets to 0 after a block is found**: upstream CKPool zeroes the "current round" best diff on solve. Kamado ships a patch that also exposes the all-time best, so the dashboard has both columns.
- **Miner rejects the TLS certificate**: re-check that the PEM was pasted completely (including the BEGIN/END lines), or pin the SHA-256 fingerprint, or disable verification in the miner.
- **A miner set to system certificates rejects the public-domain endpoint** (`No matching trusted root certificate found`, mbedTLS `-0x2700`/`-0x3000`): StartOS is serving its own self-signed certificate because Let's Encrypt never issued one. Check `start-cli server logs | grep -i acme` for `ACME order failed`, and confirm your server is reachable on **port 443** for that domain — that is where the TLS-ALPN-01 challenge is performed, whatever port the pool runs on. A hostname that failed repeatedly may also be rate-limited; see *Reaching the pool from the internet* above. Verify what is actually served with:
```sh
openssl s_client -connect <domain>:<port> -servername <domain> </dev/null 2>/dev/null | openssl x509 -noout -issuer
```
- **Miner rejects the certificate on a public domain**: make sure it is connecting by the domain name, not by IP — the certificate is only valid for the name. If it is using the name and still fails, the firmware's CA root store may not include Let's Encrypt's ISRG Root X1; pin the certificate or use the plaintext endpoint for that miner.
- **The public domain's certificate is not being served**: it takes a few minutes after adding a domain for StartOS to complete the ACME challenge. Until it does, Kamado logs `no certificate available for <domain> yet — skipping` and keeps serving the other paths. Check that the domain's DNS points at your server and that the port is reachable from the internet.
- **The public domain's certificate is not being served**: it takes a few minutes after adding a domain for StartOS to complete the ACME challenge, and the certificate is the OS's to issue — check the domain on the **Stratum (TLS, Public Domain)** interface, that its DNS points at your server, and that the port is reachable from the internet. The other endpoints keep serving meanwhile.
## Upstream
+18 -5
View File
@@ -3,6 +3,7 @@ import { i18n } from '../i18n'
import { sdk } from '../sdk'
import {
defaultStratumPort,
defaultStratumPublicTlsPort,
defaultStratumTlsPort,
logLevels,
validatePorts,
@@ -12,9 +13,9 @@ const { InputSpec, Value } = sdk
export const inputSpec = InputSpec.of({
stratumPort: Value.number({
name: i18n('Stratum Port'),
name: i18n('Preferred Stratum Port'),
description: i18n(
'Network port your miners connect to for plaintext stratum. StartOS publishes the pool on this port; if it is already claimed by another service the OS assigns a different one, so check the Stratum interface after saving. Changing this does not interrupt connected miners.',
'Network port to publish plaintext stratum on. This is a request, not a guarantee: if the port is already claimed the OS assigns a different one and warns you. Run the Pool Status action to see the ports actually in use. Changing this does not interrupt connected miners.',
),
required: true,
default: defaultStratumPort,
@@ -23,9 +24,9 @@ export const inputSpec = InputSpec.of({
max: 65535,
}),
stratumTlsPort: Value.number({
name: i18n('Stratum TLS Port'),
name: i18n('Preferred Stratum TLS Port (Local Network)'),
description: i18n(
'Network port your miners connect to for TLS stratum — both for the local self-signed certificate and for any public domain attached to the Stratum (TLS) interface.',
'Network port to publish local-network TLS stratum on, using the self-signed certificate. A request, not a guarantee — see Pool Status for the port actually in use.',
),
required: true,
default: defaultStratumTlsPort,
@@ -33,6 +34,17 @@ export const inputSpec = InputSpec.of({
min: 1,
max: 65535,
}),
stratumPublicTlsPort: Value.number({
name: i18n('Preferred Stratum TLS Port (Public Domain)'),
description: i18n(
'Network port to publish public-domain TLS stratum on. A domain attached to the Stratum (TLS, Public Domain) interface is published on THIS port, which can differ from the one the OS assigns for IP access, and StartOS issues a Lets Encrypt certificate for it — miners validate it with nothing pasted in. See Pool Status for the ports and domains actually in use.',
),
required: true,
default: defaultStratumPublicTlsPort,
integer: true,
min: 1,
max: 65535,
}),
coinbaseTag: Value.text({
name: i18n('Coinbase Tag'),
description: i18n(
@@ -56,7 +68,7 @@ export const inputSpec = InputSpec.of({
tlsEnabled: Value.toggle({
name: i18n('Stratum TLS (Local Network)'),
description: i18n(
'Serve the self-signed certificate on the TLS stratum port, for miners on your local network. Miners have to trust it (see the Stratum TLS Certificate action) or connect with verification disabled. You do NOT need this for miners connecting over a public domain: attach the domain to the Stratum (TLS) interface and StartOS issues a publicly trusted certificate automatically.',
'Serve the self-signed certificate on the local-network TLS port. Miners have to trust it (see the Stratum TLS Certificate action) or connect with verification disabled. You do NOT need this for miners connecting over a public domain: attach the domain to the Stratum (TLS, Public Domain) interface instead, and StartOS issues a publicly trusted certificate for it automatically.',
),
default: false,
}),
@@ -151,6 +163,7 @@ export const config = sdk.Action.withInput(
const conflict = validatePorts({
stratumPort: input.stratumPort,
stratumTlsPort: input.stratumTlsPort,
stratumPublicTlsPort: input.stratumPublicTlsPort,
})
if (conflict) throw new Error(conflict)
+42 -3
View File
@@ -1,7 +1,14 @@
import { storeJson } from '../fileModels/store.json'
import { i18n } from '../i18n'
import { sdk } from '../sdk'
import { curlJson, defaultStratumPort, uiPort } from '../utils'
import {
curlJson,
defaultStratumPort,
defaultStratumPublicTlsPort,
defaultStratumTlsPort,
endpointPorts,
uiPort,
} from '../utils'
const API_BASE = `http://127.0.0.1:${uiPort}`
@@ -158,8 +165,22 @@ export const poolStatus = sdk.Action.withoutInput(
// the execution function
async ({ effects }) => {
const stratumPort =
(await storeJson.read((s) => s.stratumPort).once()) ?? defaultStratumPort
// Effective external ports, straight from the OS. The Configure form holds
// a *request*; if the port was taken the OS granted a different one, and
// this is where the user finds out what miners must actually connect to.
const requests = (await storeJson
.read((s) => ({
stratum: s.stratumPort,
tls: s.stratumTlsPort,
publicTls: s.stratumPublicTlsPort,
}))
.once()) ?? {
stratum: defaultStratumPort,
tls: defaultStratumTlsPort,
publicTls: defaultStratumPublicTlsPort,
}
const ports = await endpointPorts(effects, requests, 'once')
const stratumPort = ports[0].assigned ?? ports[0].requested
// Fetch from inside the service's network namespace: temp subcontainers
// share it, so curl reaches kamado-api on 127.0.0.1.
@@ -354,6 +375,24 @@ export const poolStatus = sdk.Action.withoutInput(
// ── 2. Stratum ───────────────────────────────────────────────────────────
log('')
sep()
log('● Endpoints (the ports miners connect to)')
for (const p of ports) {
const assigned = p.assigned ?? p.requested
const note =
p.assigned === null
? ' (not assigned yet)'
: p.assigned !== p.requested
? ` <- REQUESTED ${p.requested}, reassigned by StartOS`
: ''
log(` ${pad(p.label, 30)} ${assigned}${note}`)
// Domains publish on the requested port rather than the assigned one, so
// list them explicitly instead of letting the reader assume the number
// above applies to them too.
for (const d of p.domains) {
log(` ${pad(' domain:', 30)} ${d.fqdn}:${d.port}`)
}
}
log('')
log(`● Stratum (port ${stratumPort})`)
if (snap?.ckpool_ok) {
log(
+2 -2
View File
@@ -51,7 +51,7 @@ export const showTlsCert = sdk.Action.withoutInput(
version: '1',
title: i18n('Stratum TLS Certificate'),
message: i18n(
'Connect miners with stratum+ssl:// to the Stratum (TLS) interface. The certificate is self-signed: paste the PEM into firmware that accepts a custom root, pin the fingerprint, or disable verification.',
'Connect miners with stratum+ssl:// to the Stratum (TLS, Local Network) interface. The certificate is self-signed: paste the PEM into firmware that accepts a custom root, pin the fingerprint, or disable verification. Miners connecting over a public domain do not need any of this — they use the Stratum (TLS, Public Domain) interface and its CA-issued certificate.',
),
result: {
type: 'group',
@@ -59,7 +59,7 @@ export const showTlsCert = sdk.Action.withoutInput(
{
name: i18n('TLS Port (internal)'),
description: i18n(
'Container-side TLS stratum port. The externally reachable port is shown on the Stratum (TLS) interface.',
'Container-side TLS stratum port. The externally reachable port is shown on the Stratum (TLS, Local Network) interface.',
),
type: 'single',
value: String(tlsPort),
+27 -2
View File
@@ -1,6 +1,10 @@
import { FileHelper, z } from '@start9labs/start-sdk'
import { sdk } from '../sdk'
import { defaultStratumPort, defaultStratumTlsPort } from '../utils'
import {
defaultStratumPort,
defaultStratumPublicTlsPort,
defaultStratumTlsPort,
} from '../utils'
/**
* Persisted service settings (the 0.4.0 replacement for the 0.3.x
@@ -25,13 +29,25 @@ export const storeJson = FileHelper.json(
* ckpool and never orphans a binding.
*/
stratumPort: z.number().int().min(1).max(65535).catch(defaultStratumPort),
/** EXTERNAL port for TLS stratum (only used when tlsEnabled). */
/** EXTERNAL port for TLS stratum with the self-signed (local) certificate. */
stratumTlsPort: z
.number()
.int()
.min(1)
.max(65535)
.catch(defaultStratumTlsPort),
/**
* EXTERNAL port for TLS stratum with a CA-issued certificate, terminated
* by StartOS. Separate from stratumTlsPort because the two endpoints are
* terminated by different things — the OS owns this one, stunnel owns the
* other — and a single external port cannot be handed to both.
*/
stratumPublicTlsPort: z
.number()
.int()
.min(1)
.max(65535)
.catch(defaultStratumPublicTlsPort),
/** Short string embedded in the coinbase transaction of solved blocks (ckpool btcsig). */
coinbaseTag: z.string().catch('/Kamado/'),
/** Initial vardiff target for new miner connections. */
@@ -53,5 +69,14 @@ export const storeJson = FileHelper.json(
* links. null -> use the public mempool.space.
*/
mempoolExplorerUrl: z.string().nullable().catch(null),
/**
* Internal state, not a config field: signature of the port assignment the
* user was last warned about (see portAssignmentSignature). Lets main warn
* once per distinct outcome instead of on every start, and re-warn if the
* assignment later changes. main must never read this reactively — it is
* written from main, and a reactive read would restart the service in a
* loop.
*/
notifiedPortAssignment: z.string().nullable().catch(null),
}),
)
+22
View File
@@ -90,6 +90,28 @@ const dict = {
'Network port your miners connect to for plaintext stratum. StartOS publishes the pool on this port; if it is already claimed by another service the OS assigns a different one, so check the Stratum interface after saving. Changing this does not interrupt connected miners.': 86,
'Network port your miners connect to for TLS stratum — both for the local self-signed certificate and for any public domain attached to the Stratum (TLS) interface.': 87,
'Stratum TLS (Local Network)': 88,
'Stratum TLS Port (Local Network)': 89,
'Network port for TLS stratum using the self-signed certificate, for miners on your local network.': 90,
'Stratum TLS Port (Public Domain)': 91,
'Network port for TLS stratum using a CA-issued certificate. Attach a domain to the Stratum (TLS, Public Domain) interface and StartOS issues a Lets Encrypt certificate for it — miners validate it with no certificate pasted in.': 92,
'Stratum (TLS, Public Domain)': 93,
'TLS-encrypted stratum endpoint for miners connecting over the internet. Attach a domain here and StartOS issues a Lets Encrypt certificate for it, which any miner validates with nothing pasted in': 94,
'Stratum (TLS, Local Network)': 95,
'TLS-encrypted stratum endpoint for miners on your local network, using the self-signed certificate (see the Stratum TLS Certificate action). Do not attach a public domain here — StartOS cannot issue a certificate for this endpoint; use Stratum (TLS, Public Domain) instead': 96,
'Connect miners with stratum+ssl:// to the Stratum (TLS, Local Network) interface. The certificate is self-signed: paste the PEM into firmware that accepts a custom root, pin the fingerprint, or disable verification. Miners connecting over a public domain do not need any of this — they use the Stratum (TLS, Public Domain) interface and its CA-issued certificate.': 97,
'Container-side TLS stratum port. The externally reachable port is shown on the Stratum (TLS, Local Network) interface.': 98,
'Serve the self-signed certificate on the local-network TLS port. Miners have to trust it (see the Stratum TLS Certificate action) or connect with verification disabled. You do NOT need this for miners connecting over a public domain: attach the domain to the Stratum (TLS, Public Domain) interface instead, and StartOS issues a publicly trusted certificate for it automatically.': 99,
'Preferred Stratum Port': 100,
'Preferred Stratum TLS Port (Local Network)': 101,
'Preferred Stratum TLS Port (Public Domain)': 102,
'Network port to publish plaintext stratum on. This is a request, not a guarantee: if the port is already claimed the OS assigns a different one and warns you. Run the Pool Status action to see the ports actually in use. Changing this does not interrupt connected miners.': 103,
'Network port to publish local-network TLS stratum on, using the self-signed certificate. A request, not a guarantee — see Pool Status for the port actually in use.': 104,
'Network port to publish public-domain TLS stratum on. Attach a domain to the Stratum (TLS, Public Domain) interface and StartOS issues a Lets Encrypt certificate for it — miners validate it with no certificate pasted in. A request, not a guarantee — see Pool Status for the port actually in use.': 105,
'Stratum port changed by StartOS': 106,
'{label}: requested {requested}, assigned {assigned}': 107,
'The port you asked for was already in use, so StartOS assigned another one. Point your miners at the assigned port, or pick a free one in Configure.': 108,
'Network port to publish public-domain TLS stratum on. A domain attached to the Stratum (TLS, Public Domain) interface is published on THIS port (not the one the OS assigns for IP access), and StartOS issues a Lets Encrypt certificate for it — miners validate it with nothing pasted in. Reaching it from the internet also requires enabling that interfaces public address, which is off by default. See Pool Status for the ports and domains actually in use.': 109,
'Network port to publish public-domain TLS stratum on. A domain attached to the Stratum (TLS, Public Domain) interface is published on THIS port, which can differ from the one the OS assigns for IP access, and StartOS issues a Lets Encrypt certificate for it — miners validate it with nothing pasted in. See Pool Status for the ports and domains actually in use.': 110,
} as const
/**
+88
View File
@@ -91,6 +91,28 @@ export default {
86: 'Puerto de red al que se conectan sus mineros para stratum sin cifrar. StartOS publica el pool en este puerto; si ya está ocupado por otro servicio, el sistema asigna otro, así que revise la interfaz Stratum después de guardar. Cambiarlo no interrumpe a los mineros conectados.',
87: 'Puerto de red al que se conectan sus mineros para stratum TLS: tanto para el certificado autofirmado local como para cualquier dominio público adjunto a la interfaz Stratum (TLS).',
88: 'Stratum TLS (red local)',
89: 'Puerto TLS de stratum (red local)',
90: 'Puerto de red para stratum TLS con el certificado autofirmado, para mineros de su red local.',
91: 'Puerto TLS de stratum (dominio público)',
92: 'Puerto de red para stratum TLS con un certificado emitido por una CA. Adjunte un dominio a la interfaz Stratum (TLS, dominio público) y StartOS emitirá un certificado de Lets Encrypt para él: los mineros lo validan sin pegar ningún certificado.',
93: 'Stratum (TLS, dominio público)',
94: 'Punto de acceso stratum cifrado con TLS para mineros que se conectan por internet. Adjunte aquí un dominio y StartOS emitirá un certificado de Lets Encrypt para él, que cualquier minero valida sin pegar nada',
95: 'Stratum (TLS, red local)',
96: 'Punto de acceso stratum cifrado con TLS para mineros de su red local, con el certificado autofirmado (vea la acción Certificado TLS de Stratum). No adjunte aquí un dominio público: StartOS no puede emitir un certificado para este extremo; use Stratum (TLS, dominio público)',
97: 'Conecte los mineros con stratum+ssl:// a la interfaz Stratum (TLS, red local). El certificado es autofirmado: pegue el PEM en firmware que acepte una raíz personalizada, fije la huella o desactive la verificación. Los mineros que se conectan por un dominio público no necesitan nada de esto: usan la interfaz Stratum (TLS, dominio público) y su certificado emitido por una CA.',
98: 'Puerto TLS de stratum del lado del contenedor. El puerto accesible desde el exterior se muestra en la interfaz Stratum (TLS, red local).',
99: 'Sirve el certificado autofirmado en el puerto TLS de red local. Los mineros deben confiar en él (vea la acción Certificado TLS de Stratum) o conectarse con la verificación desactivada. NO lo necesita para mineros que se conectan por un dominio público: adjunte el dominio a la interfaz Stratum (TLS, dominio público) y StartOS emitirá automáticamente un certificado de confianza pública.',
100: 'Puerto stratum preferido',
101: 'Puerto TLS de stratum preferido (red local)',
102: 'Puerto TLS de stratum preferido (dominio público)',
103: 'Puerto de red en el que publicar el stratum sin cifrar. Es una petición, no una garantía: si el puerto ya está ocupado, el sistema asigna otro y se lo advierte. Ejecute la acción Estado del pool para ver los puertos realmente en uso. Cambiarlo no interrumpe a los mineros conectados.',
104: 'Puerto de red en el que publicar el stratum TLS de red local, con el certificado autofirmado. Una petición, no una garantía: consulte Estado del pool para ver el puerto realmente en uso.',
105: 'Puerto de red en el que publicar el stratum TLS de dominio público. Adjunte un dominio a la interfaz Stratum (TLS, dominio público) y StartOS emitirá un certificado de Lets Encrypt para él: los mineros lo validan sin pegar ningún certificado. Una petición, no una garantía: consulte Estado del pool para ver el puerto realmente en uso.',
106: 'StartOS cambió el puerto stratum',
107: '{label}: solicitado {requested}, asignado {assigned}',
108: 'El puerto que solicitó ya estaba en uso, así que StartOS asignó otro. Apunte sus mineros al puerto asignado, o elija uno libre en Configurar.',
109: 'Puerto de red en el que publicar el stratum TLS de dominio público. El dominio adjuntado a la interfaz Stratum (TLS, dominio público) se publica en ESTE puerto (no en el que el sistema asigna para el acceso por IP), y StartOS emite un certificado de Lets Encrypt para él: los mineros lo validan sin pegar nada. Para alcanzarlo desde internet también hay que habilitar la dirección pública de esa interfaz, que está desactivada por defecto. Consulte Estado del pool para ver los puertos y dominios realmente en uso.',
110: 'Puerto de red en el que publicar el stratum TLS de dominio público. El dominio adjuntado a la interfaz Stratum (TLS, dominio público) se publica en ESTE puerto, que puede diferir del que el sistema asigna para el acceso por IP, y StartOS emite un certificado de Lets Encrypt para él: los mineros lo validan sin pegar nada. Consulte Estado del pool para ver los puertos y dominios realmente en uso.',
},
de_DE: {
0: '(noch nicht erzeugt — starten Sie den Dienst einmal)',
@@ -182,6 +204,28 @@ export default {
86: 'Netzwerk-Port, über den sich Ihre Miner für unverschlüsseltes Stratum verbinden. StartOS veröffentlicht den Pool auf diesem Port; ist er bereits von einem anderen Dienst belegt, weist das System einen anderen zu — prüfen Sie daher nach dem Speichern die Stratum-Schnittstelle. Eine Änderung unterbricht verbundene Miner nicht.',
87: 'Netzwerk-Port, über den sich Ihre Miner für TLS-Stratum verbinden — sowohl für das lokale selbstsignierte Zertifikat als auch für jede an die Schnittstelle Stratum (TLS) angehängte öffentliche Domain.',
88: 'Stratum-TLS (lokales Netzwerk)',
89: 'Stratum-TLS-Port (lokales Netzwerk)',
90: 'Netzwerk-Port für TLS-Stratum mit dem selbstsignierten Zertifikat, für Miner in Ihrem lokalen Netzwerk.',
91: 'Stratum-TLS-Port (öffentliche Domain)',
92: 'Netzwerk-Port für TLS-Stratum mit einem CA-ausgestellten Zertifikat. Hängen Sie eine Domain an die Schnittstelle „Stratum (TLS, öffentliche Domain)“ an, und StartOS stellt dafür ein Lets-Encrypt-Zertifikat aus — Miner validieren es, ohne ein Zertifikat einzufügen.',
93: 'Stratum (TLS, öffentliche Domain)',
94: 'TLS-verschlüsselter Stratum-Endpunkt für Miner, die sich über das Internet verbinden. Hängen Sie hier eine Domain an, und StartOS stellt dafür ein Lets-Encrypt-Zertifikat aus, das jeder Miner ohne Einfügen validiert',
95: 'Stratum (TLS, lokales Netzwerk)',
96: 'TLS-verschlüsselter Stratum-Endpunkt für Miner in Ihrem lokalen Netzwerk, mit dem selbstsignierten Zertifikat (siehe Aktion „Stratum-TLS-Zertifikat“). Hängen Sie hier keine öffentliche Domain an — StartOS kann für diesen Endpunkt kein Zertifikat ausstellen; verwenden Sie stattdessen „Stratum (TLS, öffentliche Domain)“',
97: 'Verbinden Sie Miner mit stratum+ssl:// mit der Schnittstelle „Stratum (TLS, lokales Netzwerk)“. Das Zertifikat ist selbstsigniert: Fügen Sie das PEM in Firmware ein, die eine eigene Root akzeptiert, pinnen Sie den Fingerabdruck oder deaktivieren Sie die Verifizierung. Miner, die über eine öffentliche Domain verbinden, brauchen nichts davon — sie nutzen „Stratum (TLS, öffentliche Domain)“ und dessen CA-ausgestelltes Zertifikat.',
98: 'Containerseitiger TLS-Stratum-Port. Der extern erreichbare Port wird auf der Schnittstelle „Stratum (TLS, lokales Netzwerk)“ angezeigt.',
99: 'Stellt das selbstsignierte Zertifikat auf dem TLS-Port für das lokale Netzwerk bereit. Miner müssen ihm vertrauen (siehe Aktion „Stratum-TLS-Zertifikat“) oder ohne Verifizierung verbinden. Für Miner, die über eine öffentliche Domain verbinden, brauchen Sie das NICHT: Hängen Sie die Domain stattdessen an die Schnittstelle „Stratum (TLS, öffentliche Domain)“ an, und StartOS stellt dafür automatisch ein öffentlich vertrauenswürdiges Zertifikat aus.',
100: 'Bevorzugter Stratum-Port',
101: 'Bevorzugter Stratum-TLS-Port (lokales Netzwerk)',
102: 'Bevorzugter Stratum-TLS-Port (öffentliche Domain)',
103: 'Netzwerk-Port, auf dem unverschlüsseltes Stratum veröffentlicht wird. Dies ist eine Anfrage, keine Garantie: Ist der Port bereits belegt, weist das System einen anderen zu und warnt Sie. Führen Sie die Aktion „Pool-Status“ aus, um die tatsächlich verwendeten Ports zu sehen. Eine Änderung unterbricht verbundene Miner nicht.',
104: 'Netzwerk-Port, auf dem TLS-Stratum für das lokale Netzwerk mit dem selbstsignierten Zertifikat veröffentlicht wird. Eine Anfrage, keine Garantie — siehe „Pool-Status“ für den tatsächlich verwendeten Port.',
105: 'Netzwerk-Port, auf dem TLS-Stratum für eine öffentliche Domain veröffentlicht wird. Hängen Sie eine Domain an die Schnittstelle „Stratum (TLS, öffentliche Domain)“ an, und StartOS stellt dafür ein Lets-Encrypt-Zertifikat aus — Miner validieren es ohne eingefügtes Zertifikat. Eine Anfrage, keine Garantie — siehe „Pool-Status“ für den tatsächlich verwendeten Port.',
106: 'Stratum-Port von StartOS geändert',
107: '{label}: angefordert {requested}, zugewiesen {assigned}',
108: 'Der angeforderte Port war bereits belegt, daher hat StartOS einen anderen zugewiesen. Richten Sie Ihre Miner auf den zugewiesenen Port aus, oder wählen Sie unter „Konfigurieren“ einen freien Port.',
109: 'Netzwerk-Port, auf dem TLS-Stratum für eine öffentliche Domain veröffentlicht wird. Eine an die Schnittstelle „Stratum (TLS, öffentliche Domain)“ angehängte Domain wird auf DIESEM Port veröffentlicht (nicht auf dem, den das System für den IP-Zugriff zuweist), und StartOS stellt dafür ein Lets-Encrypt-Zertifikat aus — Miner validieren es ohne eingefügtes Zertifikat. Für die Erreichbarkeit aus dem Internet muss zusätzlich die öffentliche Adresse dieser Schnittstelle aktiviert werden, die standardmäßig deaktiviert ist. Siehe „Pool-Status“ für die tatsächlich verwendeten Ports und Domains.',
110: 'Netzwerk-Port, auf dem TLS-Stratum für eine öffentliche Domain veröffentlicht wird. Eine an die Schnittstelle „Stratum (TLS, öffentliche Domain)“ angehängte Domain wird auf DIESEM Port veröffentlicht, der sich von dem für den IP-Zugriff zugewiesenen unterscheiden kann, und StartOS stellt dafür ein Lets-Encrypt-Zertifikat aus — Miner validieren es ohne eingefügtes Zertifikat. Siehe „Pool-Status“ für die tatsächlich verwendeten Ports und Domains.',
},
pl_PL: {
0: '(jeszcze nie wygenerowano — uruchom usługę raz)',
@@ -273,6 +317,28 @@ export default {
86: 'Port sieciowy, na który łączą się górnicy dla nieszyfrowanego stratum. StartOS udostępnia pulę na tym porcie; jeśli jest już zajęty przez inną usługę, system przydzieli inny — sprawdź interfejs Stratum po zapisaniu. Zmiana nie przerywa połączeń górników.',
87: 'Port sieciowy, na który łączą się górnicy dla stratum TLS — zarówno dla lokalnego certyfikatu samopodpisanego, jak i dla dowolnej domeny publicznej podłączonej do interfejsu Stratum (TLS).',
88: 'Stratum TLS (sieć lokalna)',
89: 'Port TLS stratum (sieć lokalna)',
90: 'Port sieciowy dla stratum TLS z certyfikatem samopodpisanym, dla górników w sieci lokalnej.',
91: 'Port TLS stratum (domena publiczna)',
92: 'Port sieciowy dla stratum TLS z certyfikatem wystawionym przez CA. Podłącz domenę do interfejsu Stratum (TLS, domena publiczna), a StartOS wystawi dla niej certyfikat Lets Encrypt — górnicy weryfikują go bez wklejania certyfikatu.',
93: 'Stratum (TLS, domena publiczna)',
94: 'Szyfrowany TLS punkt końcowy stratum dla górników łączących się przez internet. Podłącz tu domenę, a StartOS wystawi dla niej certyfikat Lets Encrypt, który każdy górnik weryfikuje bez wklejania czegokolwiek',
95: 'Stratum (TLS, sieć lokalna)',
96: 'Szyfrowany TLS punkt końcowy stratum dla górników w sieci lokalnej, z certyfikatem samopodpisanym (zobacz akcję Certyfikat TLS Stratum). Nie podłączaj tu domeny publicznej — StartOS nie może wystawić certyfikatu dla tego punktu; użyj Stratum (TLS, domena publiczna)',
97: 'Podłącz górników przez stratum+ssl:// do interfejsu Stratum (TLS, sieć lokalna). Certyfikat jest samopodpisany: wklej PEM do firmware akceptującego własny certyfikat główny, przypnij odcisk lub wyłącz weryfikację. Górnicy łączący się przez domenę publiczną nie potrzebują tego wszystkiego — używają interfejsu Stratum (TLS, domena publiczna) i jego certyfikatu wystawionego przez CA.',
98: 'Port TLS stratum po stronie kontenera. Port osiągalny z zewnątrz jest widoczny w interfejsie Stratum (TLS, sieć lokalna).',
99: 'Udostępnia certyfikat samopodpisany na porcie TLS sieci lokalnej. Górnicy muszą mu zaufać (zobacz akcję Certyfikat TLS Stratum) lub łączyć się z wyłączoną weryfikacją. NIE jest to potrzebne górnikom łączącym się przez domenę publiczną: podłącz domenę do interfejsu Stratum (TLS, domena publiczna), a StartOS automatycznie wystawi dla niej publicznie zaufany certyfikat.',
100: 'Preferowany port stratum',
101: 'Preferowany port TLS stratum (sieć lokalna)',
102: 'Preferowany port TLS stratum (domena publiczna)',
103: 'Port sieciowy, na którym publikowane jest nieszyfrowane stratum. To prośba, nie gwarancja: jeśli port jest już zajęty, system przydzieli inny i ostrzeże Cię. Uruchom akcję Stan puli, aby zobaczyć faktycznie używane porty. Zmiana nie przerywa połączeń górników.',
104: 'Port sieciowy, na którym publikowane jest stratum TLS sieci lokalnej z certyfikatem samopodpisanym. Prośba, nie gwarancja — zobacz Stan puli, aby poznać faktycznie używany port.',
105: 'Port sieciowy, na którym publikowane jest stratum TLS domeny publicznej. Podłącz domenę do interfejsu Stratum (TLS, domena publiczna), a StartOS wystawi dla niej certyfikat Lets Encrypt — górnicy weryfikują go bez wklejania certyfikatu. Prośba, nie gwarancja — zobacz Stan puli, aby poznać faktycznie używany port.',
106: 'StartOS zmienił port stratum',
107: '{label}: żądano {requested}, przydzielono {assigned}',
108: 'Żądany port był już zajęty, więc StartOS przydzielił inny. Skieruj górników na przydzielony port lub wybierz wolny w akcji Konfiguruj.',
109: 'Port sieciowy, na którym publikowane jest stratum TLS domeny publicznej. Domena podłączona do interfejsu Stratum (TLS, domena publiczna) jest publikowana na TYM porcie (nie na tym, który system przydziela dla dostępu po IP), a StartOS wystawia dla niej certyfikat Lets Encrypt — górnicy weryfikują go bez wklejania czegokolwiek. Aby był osiągalny z internetu, trzeba też włączyć publiczny adres tego interfejsu, domyślnie wyłączony. Zobacz Stan puli, aby poznać faktycznie używane porty i domeny.',
110: 'Port sieciowy, na którym publikowane jest stratum TLS domeny publicznej. Domena podłączona do interfejsu Stratum (TLS, domena publiczna) jest publikowana na TYM porcie, który może różnić się od przydzielonego dla dostępu po IP, a StartOS wystawia dla niej certyfikat Lets Encrypt — górnicy weryfikują go bez wklejania czegokolwiek. Zobacz Stan puli, aby poznać faktycznie używane porty i domeny.',
},
fr_FR: {
0: '(pas encore généré — démarrez le service une fois)',
@@ -364,5 +430,27 @@ export default {
86: 'Port réseau auquel vos mineurs se connectent pour le stratum en clair. StartOS publie le pool sur ce port ; sil est déjà pris par un autre service, le système en attribue un autre — vérifiez donc linterface Stratum après enregistrement. Le modifier ninterrompt pas les mineurs connectés.',
87: 'Port réseau auquel vos mineurs se connectent pour le stratum TLS — aussi bien pour le certificat auto-signé local que pour tout domaine public rattaché à linterface Stratum (TLS).',
88: 'Stratum TLS (réseau local)',
89: 'Port TLS stratum (réseau local)',
90: 'Port réseau pour le stratum TLS avec le certificat auto-signé, pour les mineurs de votre réseau local.',
91: 'Port TLS stratum (domaine public)',
92: 'Port réseau pour le stratum TLS avec un certificat émis par une AC. Rattachez un domaine à linterface Stratum (TLS, domaine public) et StartOS émet un certificat Lets Encrypt pour celui-ci — les mineurs le valident sans coller aucun certificat.',
93: 'Stratum (TLS, domaine public)',
94: 'Point de terminaison stratum chiffré en TLS pour les mineurs se connectant via internet. Rattachez-y un domaine et StartOS émet un certificat Lets Encrypt que tout mineur valide sans rien coller',
95: 'Stratum (TLS, réseau local)',
96: 'Point de terminaison stratum chiffré en TLS pour les mineurs de votre réseau local, avec le certificat auto-signé (voir laction Certificat TLS Stratum). Ny rattachez pas de domaine public — StartOS ne peut pas émettre de certificat pour ce point de terminaison ; utilisez plutôt Stratum (TLS, domaine public)',
97: 'Connectez les mineurs avec stratum+ssl:// à linterface Stratum (TLS, réseau local). Le certificat est auto-signé : collez le PEM dans un firmware acceptant une racine personnalisée, épinglez lempreinte ou désactivez la vérification. Les mineurs passant par un domaine public nont besoin de rien de tout cela — ils utilisent linterface Stratum (TLS, domaine public) et son certificat émis par une AC.',
98: 'Port stratum TLS côté conteneur. Le port accessible de lextérieur est indiqué sur linterface Stratum (TLS, réseau local).',
99: 'Présente le certificat auto-signé sur le port TLS du réseau local. Les mineurs doivent lui faire confiance (voir laction Certificat TLS Stratum) ou se connecter sans vérification. Ce nest PAS nécessaire pour les mineurs passant par un domaine public : rattachez plutôt le domaine à linterface Stratum (TLS, domaine public), et StartOS émet automatiquement un certificat publiquement approuvé.',
100: 'Port stratum préféré',
101: 'Port TLS stratum préféré (réseau local)',
102: 'Port TLS stratum préféré (domaine public)',
103: 'Port réseau sur lequel publier le stratum en clair. Cest une demande, pas une garantie : si le port est déjà pris, le système en attribue un autre et vous avertit. Exécutez laction État du pool pour voir les ports réellement utilisés. Le modifier ninterrompt pas les mineurs connectés.',
104: 'Port réseau sur lequel publier le stratum TLS du réseau local, avec le certificat auto-signé. Une demande, pas une garantie — voir État du pool pour le port réellement utilisé.',
105: 'Port réseau sur lequel publier le stratum TLS pour domaine public. Rattachez un domaine à linterface Stratum (TLS, domaine public) et StartOS émet un certificat Lets Encrypt — les mineurs le valident sans coller de certificat. Une demande, pas une garantie — voir État du pool pour le port réellement utilisé.',
106: 'Port stratum modifié par StartOS',
107: '{label} : demandé {requested}, attribué {assigned}',
108: 'Le port demandé était déjà utilisé, StartOS en a donc attribué un autre. Pointez vos mineurs vers le port attribué, ou choisissez-en un libre dans Configurer.',
109: 'Port réseau sur lequel publier le stratum TLS pour domaine public. Un domaine rattaché à linterface Stratum (TLS, domaine public) est publié sur CE port (et non sur celui que le système attribue pour laccès par IP), et StartOS émet un certificat Lets Encrypt — les mineurs le valident sans rien coller. Pour y accéder depuis internet, il faut également activer ladresse publique de cette interface, désactivée par défaut. Voir État du pool pour les ports et domaines réellement utilisés.',
110: 'Port réseau sur lequel publier le stratum TLS pour domaine public. Un domaine rattaché à linterface Stratum (TLS, domaine public) est publié sur CE port, qui peut différer de celui attribué par le système pour laccès par IP, et StartOS émet un certificat Lets Encrypt — les mineurs le valident sans rien coller. Voir État du pool pour les ports et domaines réellement utilisés.',
},
} satisfies Record<string, LangDict>
+51 -5
View File
@@ -2,10 +2,13 @@ import { storeJson } from './fileModels/store.json'
import { i18n } from './i18n'
import { sdk } from './sdk'
import {
ckpoolPublicTlsPort,
defaultStratumPort,
defaultStratumPublicTlsPort,
defaultStratumTlsPort,
stratumHostId,
stratumInternalPort,
stratumPublicTlsHostId,
stratumTlsHostId,
stratumTlsInternalPort,
uiHostId,
@@ -25,11 +28,13 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
.read((s) => ({
stratum: s.stratumPort,
stratumTls: s.stratumTlsPort,
publicTls: s.stratumPublicTlsPort,
}))
.const(effects)
const externalStratumPort = ports?.stratum ?? defaultStratumPort
const externalStratumTlsPort = ports?.stratumTls ?? defaultStratumTlsPort
const externalPublicTlsPort = ports?.publicTls ?? defaultStratumPublicTlsPort
// Web dashboard
const uiMulti = sdk.MultiHost.of(effects, uiHostId)
@@ -97,10 +102,10 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
secure: { ssl: false },
})
const stratumTls = sdk.createInterface(effects, {
name: i18n('Stratum (TLS)'),
name: i18n('Stratum (TLS, Local Network)'),
id: 'stratum-tls',
description: i18n(
'TLS-encrypted stratum endpoint. Miners on a public domain attached here get a CA-issued certificate automatically; on the local network the self-signed certificate is used (see the Stratum TLS Certificate action)',
'TLS-encrypted stratum endpoint for miners on your local network, using the self-signed certificate (see the Stratum TLS Certificate action). Do not attach a public domain here — StartOS cannot issue a certificate for this endpoint; use Stratum (TLS, Public Domain) instead',
),
type: 'api',
masked: false,
@@ -111,8 +116,48 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
})
receipts.push(await tlsOrigin.export([stratumTls]))
// Drop bindings we no longer use. With fixed internal ports and an
// unconditional TLS binding, all three are now permanent — this only
// TLS stratum over a public domain — terminated by StartOS, not by us.
//
// This is the only way to get a publicly-trusted certificate: StartOS
// provisions ACME certificates solely for bindings it terminates TLS for,
// so a raw TCP binding (the two above) is offered no certificate authority
// at all when you attach a domain to it — no Let's Encrypt, not even the
// StartOS root CA. Declaring `addSsl` hands the OS the TLS layer, which is
// what makes the LE option appear in the domain dialog.
//
// `secure: null` keeps this endpoint TLS-only: the OS publishes the SSL
// port and does not also expose the decrypted one. It forwards plaintext
// over the LXC bridge into ckpool's third bind, whose serverurl index is
// what tells the dashboard this miner arrived on a CA-issued certificate.
const publicTlsMulti = sdk.MultiHost.of(effects, stratumPublicTlsHostId)
const publicTlsOrigin = await publicTlsMulti.bindPort(ckpoolPublicTlsPort, {
protocol: null,
preferredExternalPort: ckpoolPublicTlsPort,
addSsl: {
preferredExternalPort: externalPublicTlsPort,
alpn: null,
addXForwardedHeaders: false,
auth: null,
},
secure: null,
})
const stratumPublicTls = sdk.createInterface(effects, {
name: i18n('Stratum (TLS, Public Domain)'),
id: 'stratum-tls-public',
description: i18n(
'TLS-encrypted stratum endpoint for miners connecting over the internet. Attach a domain here and StartOS issues a Lets Encrypt certificate for it, which any miner validates with nothing pasted in',
),
type: 'api',
masked: false,
schemeOverride: { ssl: 'stratum+ssl', noSsl: 'stratum+ssl' },
username: null,
path: '',
query: {},
})
receipts.push(await publicTlsOrigin.export([stratumPublicTls]))
// Drop bindings we no longer use. With fixed internal ports and
// unconditional TLS bindings, all four are now permanent — this only
// clears orphans left by older versions of this package, which did move the
// internal port and did drop the TLS binding when the toggle was off.
await sdk.clearBindings(effects, {
@@ -120,6 +165,7 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
{ id: uiHostId, internalPort: uiPort },
{ id: stratumHostId, internalPort: stratumInternalPort },
{ id: stratumTlsHostId, internalPort: stratumTlsInternalPort },
{ id: stratumPublicTlsHostId, internalPort: ckpoolPublicTlsPort },
],
})
@@ -128,7 +174,7 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
// leaves an orphaned interface record behind, which the UI still lists — so
// a port change can produce two identical "Stratum" rows.
await effects.clearServiceInterfaces({
except: ['ui', 'stratum', 'stratum-tls'],
except: ['ui', 'stratum', 'stratum-tls', 'stratum-tls-public'],
})
return receipts
+85 -74
View File
@@ -19,11 +19,11 @@ import {
parseCookie,
tlsDir,
ckpoolTlsLoopbackPort,
ckpoolPublicTlsLoopbackPort,
publicCertPath,
endpointPorts,
portAssignmentSignature,
publicDomains,
stratumInternalPort,
stratumTlsHostId,
stratumPublicTlsHostId,
stratumTlsInternalPort,
stratumServers,
stratumServerUrls,
@@ -66,11 +66,68 @@ export const main = sdk.setupMain(async ({ effects }) => {
// changes ports — and never on a routine bitcoind update.
const bitcoind = await bitcoindBridge(effects)
// Clearnet domains the user attached to the Stratum (TLS) interface. There
// is no config field for this: the domain is added in the StartOS interface
// UI, and attaching or removing one restarts main through the same reactive
// mechanism as everything else above.
const tlsDomains = await publicDomains(effects, stratumTlsHostId).const()
// Clearnet domains attached to the Stratum (TLS, Public Domain) interface.
// There is no config field for this: the domain is added in the StartOS
// interface UI, and attaching or removing one restarts main through the same
// reactive mechanism as everything else above.
//
// Used only to label the connection in the dashboard — the certificates
// themselves are the OS's concern now, so this never gates anything starting.
const tlsDomains = await publicDomains(
effects,
stratumPublicTlsHostId,
).const()
// Warn when the OS could not grant a port we asked for.
//
// `preferredExternalPort` is a request: if the number is already claimed the
// OS silently assigns another, and the first symptom is a miner that cannot
// connect on the port the config form shows. Surfacing it here turns a
// silent substitution into something the user is told about once, naming
// both numbers. Pool Status prints the effective ports on demand.
//
// Read `.const()` so a later reassignment re-fires this; the store field
// that de-dupes the warning is read `.once()` and is deliberately absent
// from the projection above, so writing it cannot restart the service.
const portRequests = await storeJson
.read((s) => ({
stratum: s.stratumPort,
tls: s.stratumTlsPort,
publicTls: s.stratumPublicTlsPort,
}))
.const(effects)
if (portRequests) {
const ports = await endpointPorts(effects, portRequests, 'const')
const mismatched = ports.filter(
(p) => p.assigned !== null && p.assigned !== p.requested,
)
const signature = portAssignmentSignature(ports)
const lastNotified = await storeJson
.read((s) => s.notifiedPortAssignment)
.once()
if (mismatched.length > 0 && signature !== lastNotified) {
await sdk.notification.create(effects, {
level: 'warning',
title: i18n('Stratum port changed by StartOS'),
message: mismatched
.map((p) =>
i18n('{label}: requested {requested}, assigned {assigned}')
.replace('{label}', p.label)
.replace('{requested}', String(p.requested))
.replace('{assigned}', String(p.assigned)),
)
.concat(
i18n(
'The port you asked for was already in use, so StartOS assigned another one. Point your miners at the assigned port, or pick a free one in Configure.',
),
)
.join('\n'),
})
}
if (signature !== lastNotified)
await storeJson.merge(effects, { notifiedPortAssignment: signature })
}
// All Kamado processes (kamado-api, ckpool, stunnel) share ONE
// subcontainer, mirroring the single 0.3.x container: kamado-api reaches
@@ -172,44 +229,21 @@ export const main = sdk.setupMain(async ({ effects }) => {
ckpoolConfTemplate,
)
// Pull the OS-managed certificate for each attached public domain. StartOS
// provisions these over ACME, so they chain to a public CA and any miner
// with a normal root store validates them with nothing pasted in.
// Public-domain TLS is StartOS's job, not ours — see the stratum-tls-public
// interface in interfaces.ts. The OS terminates ACME-backed TLS and forwards
// plaintext into ckpool's third bind, so nothing here fetches, writes or
// serves a certificate for a public domain.
//
// Failures are per-domain and non-fatal: right after a domain is added the
// certificate may not be issued yet (DNS still propagating, ACME challenge
// pending). Skipping it leaves the rest of the pool running and the Stratum
// TLS health check reports the shortfall, rather than a domain typo taking
// the whole stratum server down.
// This package used to do that itself with sdk.getSslCertificate() plus
// stunnel SNI sections, which cannot work: StartOS only provisions ACME
// certificates for bindings it terminates TLS for, so a raw TCP binding was
// handed no CA-issued certificate to serve and miners got the self-signed
// one (mbedtls -0x2700, X509_CERT_VERIFY_FAILED).
//
// stunnel is therefore left with exactly one job: the self-signed
// certificate for miners on the local network.
await mkdir(`${kamadoSub.rootfs}${stunnelConfDir}`, { recursive: true })
const publicCerts: { fqdn: string; path: string }[] = []
for (const fqdn of tlsDomains) {
try {
const chain = await sdk.getSslCertificate(effects, [fqdn]).const()
const key = await sdk.getSslKey(effects, { hostnames: [fqdn] })
const path = publicCertPath(fqdn)
// stunnel takes the chain and the key from a single file. Concatenating
// the whole fullchain means the server presents its intermediates,
// which is what lets a miner validate without a pinned copy.
await writeFile(
`${kamadoSub.rootfs}${path}`,
[...chain, key].join('\n'),
{ mode: 0o600 },
)
publicCerts.push({ fqdn, path })
console.info(`kamado-tls: serving CA-issued certificate for ${fqdn}`)
} catch (e) {
console.error(
`kamado-tls: no certificate available for ${fqdn} yet — skipping`,
e,
)
}
}
// stunnel runs when there is at least one certificate to serve. The two
// paths are independent: local TLS is the user's toggle, public TLS follows
// whatever domains are attached to the interface.
const stunnelEnabled = store.tlsEnabled || publicCerts.length > 0
const stunnelEnabled = store.tlsEnabled
// stunnel.conf is rendered here rather than shipped as a static asset so it
// stays next to the ports it references. `accept` is the fixed in-container
@@ -226,19 +260,6 @@ export const main = sdk.setupMain(async ({ effects }) => {
// tags the two paths with different serverurl indices, which is how the
// dashboard's lock badge can name the certificate in use.
if (stunnelEnabled) {
// Local TLS owns the primary service when it's on. With it off, the first
// public certificate takes over as the default so the port still answers
// a no-SNI client (encrypted, just not verifiable against a bare IP).
const primary = store.tlsEnabled
? { cert: `${tlsDir}/stratum.pem`, connect: ckpoolTlsLoopbackPort }
: {
cert: publicCerts[0].path,
connect: ckpoolPublicTlsLoopbackPort,
}
// Whichever domain was promoted to primary must not also appear as a
// secondary — stunnel would be routing an SNI name to itself.
const sniCerts = store.tlsEnabled ? publicCerts : publicCerts.slice(1)
const stunnelConf = [
'foreground = yes',
'pid =',
@@ -259,20 +280,12 @@ export const main = sdk.setupMain(async ({ effects }) => {
'',
'[stratum]',
`accept = 0.0.0.0:${stratumTlsInternalPort}`,
`connect = 127.0.0.1:${primary.connect}`,
`cert = ${primary.cert}`,
`connect = 127.0.0.1:${ckpoolTlsLoopbackPort}`,
`cert = ${tlsDir}/stratum.pem`,
// No client-cert auth — stratum over TLS is opportunistic encryption;
// the stratum protocol layer handles miner auth via username.
'verify = 0',
'',
...sniCerts.flatMap(({ fqdn, path }) => [
`[stratum-${fqdn}]`,
`sni = stratum:${fqdn}`,
`connect = 127.0.0.1:${ckpoolPublicTlsLoopbackPort}`,
`cert = ${path}`,
'verify = 0',
'',
]),
].join('\n')
await writeFile(
@@ -325,9 +338,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
// the lock badge can name the certificate a miner is using instead
// of assuming a bind order. Labels only mention domains we actually
// managed to load a certificate for.
STRATUM_SERVERS: JSON.stringify(
stratumServers(publicCerts.map((c) => c.fqdn)),
),
STRATUM_SERVERS: JSON.stringify(stratumServers(tlsDomains)),
},
},
ready: {
@@ -492,10 +503,10 @@ export const main = sdk.setupMain(async ({ effects }) => {
},
),
},
// The self-signed generator only runs when local TLS is on; with
// only public certificates configured there is no 'tls-cert'
// oneshot to wait for (they were written during setup above).
requires: store.tlsEnabled ? ['tls-cert'] : ['dirs'],
// stunnel now runs only when local TLS is on, and that is exactly
// when the self-signed certificate it serves is generated — so the
// oneshot is always the dependency.
requires: ['tls-cert'],
}
: null,
)
+145 -16
View File
@@ -40,8 +40,17 @@ export const stratumTlsInternalPort = 3334
*/
/** Self-signed certificate, for miners on the local network. */
export const ckpoolTlsLoopbackPort = 3437
/** CA-issued certificate for a StartOS public domain (ACME / Let's Encrypt). */
export const ckpoolPublicTlsLoopbackPort = 3438
/**
* CA-issued certificate for a StartOS public domain (ACME / Let's Encrypt).
*
* Unlike the self-signed bind above this one listens on 0.0.0.0, because the
* terminator in front of it is StartOS itself rather than our in-container
* stunnel: the OS decrypts on the public interface and forwards over the LXC
* bridge, which never arrives on loopback. It has to be the OS — StartOS only
* provisions ACME certificates for bindings it terminates TLS for, so a raw
* TCP binding is offered no certificate authority at all in the UI.
*/
export const ckpoolPublicTlsPort = 3438
/**
* Defaults for the user-facing *external* ports (see store.json). These are
@@ -51,6 +60,8 @@ export const ckpoolPublicTlsLoopbackPort = 3438
*/
export const defaultStratumPort = 3333
export const defaultStratumTlsPort = 3334
/** External port for the OS-terminated (CA-issued) TLS endpoint. */
export const defaultStratumPublicTlsPort = 3335
/**
* Reject external-port choices that cannot work. Since the user no longer
@@ -66,11 +77,20 @@ export const defaultStratumTlsPort = 3334
export function validatePorts(opts: {
stratumPort: number
stratumTlsPort: number
stratumPublicTlsPort: number
}): string | null {
const { stratumPort, stratumTlsPort } = opts
const { stratumPort, stratumTlsPort, stratumPublicTlsPort } = opts
if (stratumPort === stratumTlsPort)
return `The stratum port and the stratum TLS port must differ (both are ${stratumPort}).`
const claimed: [number, string][] = [
[stratumPort, 'Preferred Stratum Port'],
[stratumTlsPort, 'Preferred Stratum TLS Port (Local Network)'],
[stratumPublicTlsPort, 'Preferred Stratum TLS Port (Public Domain)'],
]
for (let i = 0; i < claimed.length; i++)
for (let j = i + 1; j < claimed.length; j++)
if (claimed[i][0] === claimed[j][0])
return `${claimed[i][1]} and ${claimed[j][1]} must differ (both are ${claimed[i][0]}).`
return null
}
@@ -101,7 +121,7 @@ export function stratumServerUrls(): string[] {
return [
`0.0.0.0:${stratumInternalPort}`,
`127.0.0.1:${ckpoolTlsLoopbackPort}`,
`127.0.0.1:${ckpoolPublicTlsLoopbackPort}`,
`0.0.0.0:${ckpoolPublicTlsPort}`,
]
}
@@ -130,10 +150,125 @@ export function stratumServers(publicDomains: string[]): StratumServer[] {
]
}
// ── Effective (OS-assigned) external ports ───────────────────────────────────
/**
* What a stratum endpoint is actually reachable on, versus what we asked for.
*
* `preferredExternalPort` is a request, not a reservation: if the port is
* already claimed the OS grants a different one and nothing fails. Every
* consumer of that fact reads it here, so the config form can express intent
* while the Pool Status action and the mismatch warning report the truth.
*/
export type EndpointPort = {
/** Interface name as it appears in the StartOS UI. */
label: string
hostId: string
/** What the user asked for in Configure. */
requested: number
/** What the OS granted, or null while the binding has no assignment yet. */
assigned: number | null
/**
* Public domains attached to this binding, with the port each is published
* on. That port follows the *requested* value, so it can differ from
* `assigned` — the domain and the IP addresses of one interface are not
* necessarily reachable on the same number.
*/
domains: { fqdn: string; port: number }[]
}
/**
* Resolve all three stratum endpoints. `mode` picks the read strategy: 'const'
* in main (so a port reassignment re-fires it), 'once' in an action.
*
* The public-domain endpoint is read from `assignedSslPort` — the OS terminates
* TLS there, so the port miners connect to is the SSL one; `assignedPort` on
* that binding is the decrypted side, which is not published.
*/
export async function endpointPorts(
effects: T.Effects,
requested: { stratum: number; tls: number; publicTls: number },
mode: 'const' | 'once',
): Promise<EndpointPort[]> {
const specs: {
label: string
hostId: string
internalPort: number
requested: number
ssl: boolean
}[] = [
{
label: 'Stratum',
hostId: stratumHostId,
internalPort: stratumInternalPort,
requested: requested.stratum,
ssl: false,
},
{
label: 'Stratum (TLS, Local Network)',
hostId: stratumTlsHostId,
internalPort: stratumTlsInternalPort,
requested: requested.tls,
ssl: false,
},
{
label: 'Stratum (TLS, Public Domain)',
hostId: stratumPublicTlsHostId,
internalPort: ckpoolPublicTlsPort,
requested: requested.publicTls,
ssl: true,
},
]
return Promise.all(
specs.map(async (spec) => {
const watch = sdk.host.getOwn(effects, spec.hostId, (host) => {
const binding = host?.bindings[spec.internalPort]
const net = binding?.net
// Public domains are published on the port the binding *requested*
// (addSsl.preferredExternalPort), not the one the OS assigned — so a
// domain and an IP address on the same interface can advertise
// different ports. Read the domain entries rather than deriving them.
const domains = (binding?.addresses.available ?? [])
.filter((a) => a.metadata.kind === 'public-domain' && a.port !== null)
.map((a) => ({ fqdn: a.hostname, port: a.port as number }))
return {
assigned:
(spec.ssl ? net?.assignedSslPort : net?.assignedPort) ?? null,
domains,
}
})
const info = mode === 'const' ? await watch.const() : await watch.once()
return {
label: spec.label,
hostId: spec.hostId,
requested: spec.requested,
assigned: info?.assigned ?? null,
domains: info?.domains ?? [],
}
}),
)
}
/**
* Stable signature of the current assignment, used to notify about a
* mismatch exactly once per distinct outcome rather than on every start.
*/
export function portAssignmentSignature(ports: EndpointPort[]): string {
return ports.map((p) => `${p.hostId}:${p.requested}>${p.assigned}`).join('|')
}
// ── Host ids (the `sdk.MultiHost.of` groups) ─────────────────────────────────
export const uiHostId = 'ui'
export const stratumHostId = 'stratum'
export const stratumTlsHostId = 'stratum-tls'
/**
* Host for the OS-terminated TLS interface. Separate from stratumTlsHostId
* because the two differ in exactly the way StartOS cares about: this one
* declares `addSsl`, so the OS owns the certificate and offers Let's Encrypt
* when a domain is attached; that one is opaque TCP that stunnel terminates.
*/
export const stratumPublicTlsHostId = 'stratum-tls-public'
// ── In-container paths ───────────────────────────────────────────────────────
@@ -153,19 +288,13 @@ export const tlsDir = `${kamadoRoot}/tls`
/**
* stunnel's config directory, on the subcontainer rootfs rather than a volume.
* The OS-managed certificates for public domains are re-fetched from StartOS
* on every main run, so — like ckpool.conf and its RPC credentials — they are
* written somewhere ephemeral and never persisted. Only the self-signed
* certificate lives on the volume, because its fingerprint has to survive
* restarts for miners that pin it.
* The config is re-rendered on every main run, so — like ckpool.conf and its
* RPC credentials — it lives somewhere ephemeral. The self-signed certificate
* it serves is the one thing that stays on the volume, because its fingerprint
* has to survive restarts for miners that pin it.
*/
export const stunnelConfDir = '/etc/stunnel'
/** Path of the PEM bundle (chain + key) stunnel serves for `fqdn`. */
export function publicCertPath(fqdn: string): string {
return `${stunnelConfDir}/public-${fqdn.replace(/[^a-zA-Z0-9._-]/g, '_')}.pem`
}
/** Files that make up the persisted stratum TLS certificate (relative to the main volume) */
export const tlsVolumeFiles = [
'tls/stratum.crt',
+3 -2
View File
@@ -1,11 +1,12 @@
import { VersionGraph } from '@start9labs/start-sdk'
import { v0_2_0 } from './v0_2_0'
import { v0_2_1 } from './v0_2_1'
import { v0_2_2 } from './v0_2_2'
// The current version must be listed first; `other` carries the rest of the
// graph so StartOS can find a migration path from whatever version an existing
// install is currently on.
export const versionGraph = VersionGraph.of({
current: v0_2_1,
other: [v0_2_0],
current: v0_2_2,
other: [v0_2_0, v0_2_1],
})