Dont duplicate interfaces on port change 3.0

This commit is contained in:
2026-08-01 06:31:15 +03:00
parent be3bebeb5b
commit 703ca1b559
10 changed files with 106 additions and 85 deletions
+5 -5
View File
@@ -14,7 +14,7 @@ export const inputSpec = InputSpec.of({
stratumPort: Value.number({
name: i18n('Stratum Port'),
description: i18n(
'TCP port the plaintext stratum server listens on. StartOS also tries to publish the pool on this same port number on your network, so this is normally the port you give your miners — check the Stratum interface after saving, since the OS will pick a different external port if this one is already taken.',
'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.',
),
required: true,
default: defaultStratumPort,
@@ -25,7 +25,7 @@ export const inputSpec = InputSpec.of({
stratumTlsPort: Value.number({
name: i18n('Stratum TLS Port'),
description: i18n(
'TCP port stunnel accepts TLS stratum connections on. Only used when Stratum TLS is enabled below.',
'Network port your miners connect to for TLS stratum. Only used when Stratum TLS is enabled below.',
),
required: true,
default: defaultStratumTlsPort,
@@ -141,9 +141,9 @@ export const config = sdk.Action.withInput(
// the execution function
async ({ effects, input }) => {
// Refuse port choices that cannot bind — all of these processes share one
// network namespace, so a collision would surface as a restart loop after
// saving rather than as an error here.
// Refuse an unusable pair (both stratum ports set to the same number).
// In-container ports are fixed constants, so the user can no longer create
// a bind collision — only an ambiguous external-port request.
const conflict = validatePorts({
stratumPort: input.stratumPort,
stratumTlsPort: input.stratumTlsPort,
+5 -4
View File
@@ -19,12 +19,13 @@ export const storeJson = FileHelper.json(
},
z.object({
/**
* Port ckpool binds for plaintext stratum. Also requested as the
* interface's preferred external port, so miners reach the pool on this
* same number whenever the OS can grant it.
* EXTERNAL port miners connect to for plaintext stratum requested as the
* interface's `preferredExternalPort`. The in-container bind is a fixed
* constant (see utils.ts), so changing this rebinds without restarting
* ckpool and never orphans a binding.
*/
stratumPort: z.number().int().min(1).max(65535).catch(defaultStratumPort),
/** Port stunnel accepts TLS stratum on (only used when tlsEnabled). */
/** EXTERNAL port for TLS stratum (only used when tlsEnabled). */
stratumTlsPort: z
.number()
.int()
+2
View File
@@ -87,6 +87,8 @@ const dict = {
'TCP port the plaintext stratum server listens on. StartOS also tries to publish the pool on this same port number on your network, so this is normally the port you give your miners — check the Stratum interface after saving, since the OS will pick a different external port if this one is already taken.': 83,
'Stratum TLS Port': 84,
'TCP port stunnel accepts TLS stratum connections on. Only used when Stratum TLS is enabled below.': 85,
'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. Only used when Stratum TLS is enabled below.': 87,
} as const
/**
@@ -88,6 +88,8 @@ export default {
83: 'Puerto TCP en el que escucha el servidor stratum sin cifrar. StartOS también intenta publicar el pool en ese mismo número de puerto en su red, por lo que normalmente es el puerto que dará a sus mineros: revise la interfaz Stratum después de guardar, ya que el sistema elegirá otro puerto externo si este ya está ocupado.',
84: 'Puerto TLS de stratum',
85: 'Puerto TCP en el que stunnel acepta conexiones stratum TLS. Solo se usa cuando Stratum TLS está activado más abajo.',
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. Solo se usa cuando Stratum TLS está activado más abajo.',
},
de_DE: {
0: '(noch nicht erzeugt — starten Sie den Dienst einmal)',
@@ -176,6 +178,8 @@ export default {
83: 'TCP-Port, auf dem der unverschlüsselte Stratum-Server lauscht. StartOS versucht außerdem, den Pool unter derselben Portnummer im Netzwerk zu veröffentlichen — normalerweise ist dies also der Port für Ihre Miner. Prüfen Sie nach dem Speichern die Stratum-Schnittstelle, denn das System wählt einen anderen externen Port, falls dieser belegt ist.',
84: 'Stratum-TLS-Port',
85: 'TCP-Port, auf dem stunnel TLS-Stratum-Verbindungen annimmt. Wird nur verwendet, wenn Stratum-TLS unten aktiviert ist.',
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. Wird nur verwendet, wenn Stratum-TLS unten aktiviert ist.',
},
pl_PL: {
0: '(jeszcze nie wygenerowano — uruchom usługę raz)',
@@ -264,6 +268,8 @@ export default {
83: 'Port TCP, na którym nasłuchuje nieszyfrowany serwer stratum. StartOS próbuje również udostępnić pulę pod tym samym numerem portu w sieci, więc zwykle jest to port podawany górnikom — po zapisaniu sprawdź interfejs Stratum, ponieważ system wybierze inny port zewnętrzny, jeśli ten jest zajęty.',
84: 'Port TLS stratum',
85: 'Port TCP, na którym stunnel przyjmuje połączenia stratum TLS. Używany tylko, gdy Stratum TLS jest włączone poniżej.',
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. Używany tylko, gdy Stratum TLS jest włączone poniżej.',
},
fr_FR: {
0: '(pas encore généré — démarrez le service une fois)',
@@ -352,5 +358,7 @@ export default {
83: 'Port TCP sur lequel le serveur stratum en clair écoute. StartOS tente également de publier le pool sur ce même numéro de port sur votre réseau : cest donc normalement le port à donner à vos mineurs. Vérifiez linterface Stratum après enregistrement, car le système choisira un autre port externe si celui-ci est déjà pris.',
84: 'Port TLS stratum',
85: 'Port TCP sur lequel stunnel accepte les connexions stratum TLS. Utilisé uniquement lorsque Stratum TLS est activé ci-dessous.',
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. Utilisé uniquement lorsque Stratum TLS est activé ci-dessous.',
},
} satisfies Record<string, LangDict>
+20 -19
View File
@@ -5,15 +5,19 @@ import {
defaultStratumPort,
defaultStratumTlsPort,
stratumHostId,
stratumInternalPort,
stratumTlsHostId,
stratumTlsInternalPort,
uiHostId,
uiPort,
} from './utils'
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
// Stratum ports are user config. Read reactively so changing them in the
// Configure action re-runs this and rebinds the interfaces — the same
// mechanism that adds/removes the TLS interface when TLS is toggled.
// The user's stratum ports are EXTERNAL ports only. Read reactively so
// changing them in the Configure action re-runs this and updates the
// binding — the same mechanism that adds/removes the TLS interface when TLS
// is toggled. The in-container ports stay fixed (see utils.ts), so each
// change updates the existing binding rather than orphaning it.
const ports = await storeJson
.read((s) => ({
stratum: s.stratumPort,
@@ -22,8 +26,8 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
}))
.const(effects)
const stratumPort = ports?.stratum ?? defaultStratumPort
const stratumTlsPort = ports?.stratumTls ?? defaultStratumTlsPort
const externalStratumPort = ports?.stratum ?? defaultStratumPort
const externalStratumTlsPort = ports?.stratumTls ?? defaultStratumTlsPort
// Web dashboard
const uiMulti = sdk.MultiHost.of(effects, uiHostId)
@@ -50,9 +54,9 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
// forwards raw TCP on the LAN, so miners connect directly to the host at
// the assigned external port; no router forward or simpleproxy needed.
const stratumMulti = sdk.MultiHost.of(effects, stratumHostId)
const stratumOrigin = await stratumMulti.bindPort(stratumPort, {
const stratumOrigin = await stratumMulti.bindPort(stratumInternalPort, {
protocol: null,
preferredExternalPort: stratumPort,
preferredExternalPort: externalStratumPort,
addSsl: null,
secure: { ssl: false },
})
@@ -77,9 +81,9 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
// the noSsl scheme is deliberately stratum+ssl.
if (ports?.tlsEnabled) {
const tlsMulti = sdk.MultiHost.of(effects, stratumTlsHostId)
const tlsOrigin = await tlsMulti.bindPort(stratumTlsPort, {
const tlsOrigin = await tlsMulti.bindPort(stratumTlsInternalPort, {
protocol: null,
preferredExternalPort: stratumTlsPort,
preferredExternalPort: externalStratumTlsPort,
addSsl: null,
secure: { ssl: false },
})
@@ -99,20 +103,17 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
receipts.push(await tlsOrigin.export([stratumTls]))
}
// Drop bindings we no longer use. `bindPort` registers a binding per
// (hostId, internalPort) and nothing removes the old one when the user
// changes a port in Configure — the stale binding lingers and the OS lists
// its interface again, so the UI shows two identical "Stratum" entries (and
// keeps a forward open on the abandoned port). Packages with fixed ports
// never hit this; ours has user-configurable ports, so it must clean up
// after itself. Runs last, after the current bindings exist, so the `except`
// list is exactly what we just bound.
// Drop bindings we no longer use — primarily the stratum-tls binding after
// the user disables TLS. With fixed internal ports this no longer has to
// clean up after port changes (the whole point of keeping them fixed), but
// it still matters for the TLS toggle, and it clears orphans left by older
// versions of this package that did move the internal port.
await sdk.clearBindings(effects, {
except: [
{ id: uiHostId, internalPort: uiPort },
{ id: stratumHostId, internalPort: stratumPort },
{ id: stratumHostId, internalPort: stratumInternalPort },
...(ports?.tlsEnabled
? [{ id: stratumTlsHostId, internalPort: stratumTlsPort }]
? [{ id: stratumTlsHostId, internalPort: stratumTlsInternalPort }]
: []),
],
})
+33 -14
View File
@@ -18,7 +18,9 @@ import {
kamadoRoot,
parseCookie,
tlsDir,
tlsInternalPort,
ckpoolTlsLoopbackPort,
stratumInternalPort,
stratumTlsInternalPort,
uiPort,
} from './utils'
@@ -30,9 +32,25 @@ export const main = sdk.setupMain(async ({ effects }) => {
*/
console.info('Starting Kamado Pool!')
// Service settings; reactive, so any config-action change restarts the
// daemons with a freshly rendered ckpool.conf.
const store = await storeJson.read().const(effects)
// Service settings; reactive, so a config-action change restarts the daemons
// with a freshly rendered ckpool.conf. Deliberately a projection rather than
// the whole file: stratumPort / stratumTlsPort are EXTERNAL ports owned by
// interfaces.ts, and the in-container binds are fixed constants. Excluding
// them here means changing a port is a pure rebind that leaves the pool
// running instead of kicking every connected miner.
const store = await storeJson
.read((s) => ({
coinbaseTag: s.coinbaseTag,
startDiff: s.startDiff,
minDiff: s.minDiff,
maxDiff: s.maxDiff,
dropIdle: s.dropIdle,
logLevel: s.logLevel,
zmqEnabled: s.zmqEnabled,
tlsEnabled: s.tlsEnabled,
mempoolExplorerUrl: s.mempoolExplorerUrl,
}))
.const(effects)
if (!store) throw new Error('No store.json')
// bitcoind's RPC + ZMQ endpoints over the LXC bridge (see bitcoindBridge in
@@ -122,8 +140,8 @@ export const main = sdk.setupMain(async ({ effects }) => {
blockpoll: 100,
update_interval: 30,
serverurl: [
`0.0.0.0:${store.stratumPort}`,
`127.0.0.1:${tlsInternalPort}`,
`0.0.0.0:${stratumInternalPort}`,
`127.0.0.1:${ckpoolTlsLoopbackPort}`,
],
mindiff: store.minDiff,
startdiff: store.startDiff,
@@ -142,10 +160,11 @@ export const main = sdk.setupMain(async ({ effects }) => {
ckpoolConfTemplate,
)
// stunnel.conf is rendered here rather than shipped as a static asset,
// because the accept port is user config now. `connect` stays on ckpool's
// fixed loopback bind so TLS clients keep getting tagged server == 1 (the
// dashboard's lock icon).
// stunnel.conf is rendered here rather than shipped as a static asset so it
// stays next to the ports it references. `connect` targets ckpool's loopback
// bind so TLS clients keep getting tagged server == 1 (the dashboard's lock
// icon); `accept` is the fixed in-container TLS port, which the OS forwards
// the user's chosen external port to.
if (store.tlsEnabled) {
const stunnelConf = [
'foreground = yes',
@@ -166,8 +185,8 @@ export const main = sdk.setupMain(async ({ effects }) => {
'options = NO_TLSv1_1',
'',
'[stratum]',
`accept = 0.0.0.0:${store.stratumTlsPort}`,
`connect = 127.0.0.1:${tlsInternalPort}`,
`accept = 0.0.0.0:${stratumTlsInternalPort}`,
`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.
@@ -253,7 +272,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
display: i18n('Stratum Server'),
gracePeriod: 30_000,
fn: () =>
sdk.healthCheck.checkPortListening(effects, store.stratumPort, {
sdk.healthCheck.checkPortListening(effects, stratumInternalPort, {
successMessage: i18n('The stratum server is accepting connections'),
errorMessage: i18n(
'The stratum server is not accepting connections',
@@ -372,7 +391,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
fn: () =>
sdk.healthCheck.checkPortListening(
effects,
store.stratumTlsPort,
stratumTlsInternalPort,
{
successMessage: i18n(
'TLS stratum is accepting connections',
+22 -32
View File
@@ -17,41 +17,40 @@ import { sdk } from './sdk'
export const uiPort = 8080
/**
* Stratum port defaults. The live values are user config (see store.json) —
* each one sets both ckpool's/stunnel's in-container bind AND the interface's
* preferred external port, so the number the user picks is the number miners
* connect to whenever the OS can grant it.
* In-container bind ports. These are FIXED and never user-configurable, which
* is load-bearing: a binding is keyed by (hostId, internalPort), so moving an
* internal port registers a *new* binding and orphans the old one — StartOS
* disables the orphan but keeps listing it, and the user sees a duplicate
* interface. Keeping these constant means each host has exactly one binding
* for the lifetime of the install, and a port change is a pure rebind that
* doesn't even restart the daemons.
*/
export const defaultStratumPort = 3333
export const defaultStratumTlsPort = 3334
export const stratumInternalPort = 3333
export const stratumTlsInternalPort = 3334
/**
* ckpool's second, loopback-only stratum bind. stunnel forwards decrypted TLS
* traffic here. ckpool tags clients by serverurl index (server == 1 -> TLS),
* which the dashboard reads to render a lock icon next to encrypted miners —
* no source-IP heuristics needed. The bind is harmless when TLS is disabled
* (nothing connects to it). Never user-visible, so it stays fixed — but it
* does occupy a port inside the container, hence validatePorts() below.
* (nothing connects to it).
*/
export const tlsInternalPort = 3437
export const ckpoolTlsLoopbackPort = 3437
/**
* Ports already taken inside the service container, mapped to what occupies
* them. A user-chosen stratum port may not collide with these.
* Defaults for the user-facing *external* ports (see store.json). These are
* what miners connect to; they are requested as each interface's
* `preferredExternalPort` and the OS grants them when free. Same numbers as
* the internal binds, so the out-of-the-box experience is unchanged.
*/
const occupiedPorts: Record<number, string> = {
[uiPort]: 'the web dashboard',
[tlsInternalPort]: "ckpool's internal TLS bind",
}
export const defaultStratumPort = 3333
export const defaultStratumTlsPort = 3334
/**
* Reject stratum port choices that cannot work: the two stratum ports would
* collide with each other, or with a port already bound inside the container.
* Returns a human-readable reason, or null when the pair is usable.
*
* All of these processes share one container (and therefore one network
* namespace), so a collision is a real bind failure at startup — better to
* refuse it in the config action than to restart-loop later.
* Reject external-port choices that cannot work. Since the user no longer
* picks any container-side port, the only real conflict left is asking for the
* same external port twice. Returns a human-readable reason, or null when the
* pair is usable.
*/
export function validatePorts(opts: {
stratumPort: number
@@ -60,16 +59,7 @@ export function validatePorts(opts: {
}): string | null {
const { stratumPort, stratumTlsPort, tlsEnabled } = opts
const clash = occupiedPorts[stratumPort]
if (clash) return `Stratum port ${stratumPort} is already used by ${clash}.`
if (!tlsEnabled) return null
const tlsClash = occupiedPorts[stratumTlsPort]
if (tlsClash)
return `Stratum TLS port ${stratumTlsPort} is already used by ${tlsClash}.`
if (stratumPort === stratumTlsPort)
if (tlsEnabled && stratumPort === stratumTlsPort)
return `The stratum port and the stratum TLS port must differ (both are ${stratumPort}).`
return null
+6 -6
View File
@@ -21,18 +21,18 @@ type LegacyConfig = {
}
export const current = VersionInfo.of({
version: '0.2.0:2',
version: '0.2.0:3',
releaseNotes: {
en_US:
'Adds configurable stratum and stratum-TLS ports to the Configure action, and fixes a duplicate Stratum interface left behind when a port is changed. StartOS 0.4.0 port: stratum is exposed directly on the LAN as a raw TCP interface (no more router forwards or simpleproxy), Bitcoin Core is reached over the internal network bridge with cookie authentication, and settings moved from Config to the Configure action. Existing settings, found-block history, and the stratum TLS certificate are migrated automatically.',
'The configurable stratum ports now set only the external (network-facing) port. The in-container ports are fixed, so changing a port no longer leaves a duplicate Stratum interface behind and no longer restarts the pool — connected miners stay connected. StartOS 0.4.0 port: stratum is exposed directly on the LAN as a raw TCP interface (no more router forwards or simpleproxy), Bitcoin Core is reached over the internal network bridge with cookie authentication, and settings moved from Config to the Configure action. Existing settings, found-block history, and the stratum TLS certificate are migrated automatically.',
es_ES:
'Añade puertos stratum y stratum-TLS configurables en la acción Configurar, y corrige una interfaz Stratum duplicada que quedaba al cambiar un puerto. Adaptación a StartOS 0.4.0: Stratum se expone directamente en la LAN como interfaz TCP, Bitcoin Core se alcanza a través del puente de red interno con autenticación por cookie, y la configuración se movió a la acción Configurar. Los ajustes existentes, el historial de bloques y el certificado TLS se migran automáticamente.',
'Los puertos stratum configurables ahora solo definen el puerto externo (de red). Los puertos internos del contenedor son fijos, así que cambiar un puerto ya no deja una interfaz Stratum duplicada ni reinicia el pool: los mineros conectados siguen conectados. Adaptación a StartOS 0.4.0: Stratum se expone directamente en la LAN como interfaz TCP, Bitcoin Core se alcanza a través del puente de red interno con autenticación por cookie, y la configuración se movió a la acción Configurar. Los ajustes existentes, el historial de bloques y el certificado TLS se migran automáticamente.',
de_DE:
'Fügt konfigurierbare Stratum- und Stratum-TLS-Ports in der Aktion „Konfigurieren“ hinzu und behebt eine doppelte Stratum-Schnittstelle, die beim Ändern eines Ports zurückblieb. Portierung auf StartOS 0.4.0: Stratum wird direkt im LAN als TCP-Schnittstelle bereitgestellt, Bitcoin Core wird über die interne Netzwerk-Bridge mit Cookie-Authentifizierung erreicht, und die Einstellungen sind in die Aktion „Konfigurieren“ umgezogen. Bestehende Einstellungen, Blockhistorie und das TLS-Zertifikat werden automatisch migriert.',
'Die konfigurierbaren Stratum-Ports legen jetzt nur noch den externen (netzseitigen) Port fest. Die containerinternen Ports sind fix, sodass eine Portänderung keine doppelte Stratum-Schnittstelle hinterlässt und den Pool nicht neu startet — verbundene Miner bleiben verbunden. Portierung auf StartOS 0.4.0: Stratum wird direkt im LAN als TCP-Schnittstelle bereitgestellt, Bitcoin Core wird über die interne Netzwerk-Bridge mit Cookie-Authentifizierung erreicht, und die Einstellungen sind in die Aktion „Konfigurieren“ umgezogen. Bestehende Einstellungen, Blockhistorie und das TLS-Zertifikat werden automatisch migriert.',
pl_PL:
'Dodaje konfigurowalne porty stratum i stratum-TLS w akcji Konfiguruj oraz naprawia zduplikowany interfejs Stratum pozostający po zmianie portu. Port na StartOS 0.4.0: Stratum jest udostępniany bezpośrednio w sieci LAN jako interfejs TCP, Bitcoin Core jest osiągany przez wewnętrzny mostek sieciowy z uwierzytelnianiem cookie, a ustawienia przeniesiono do akcji Konfiguruj. Istniejące ustawienia, historia bloków i certyfikat TLS są migrowane automatycznie.',
'Konfigurowalne porty stratum ustawiają teraz wyłącznie port zewnętrzny (sieciowy). Porty wewnątrz kontenera są stałe, więc zmiana portu nie pozostawia już zduplikowanego interfejsu Stratum ani nie restartuje puli — podłączeni górnicy pozostają połączeni. Port na StartOS 0.4.0: Stratum jest udostępniany bezpośrednio w sieci LAN jako interfejs TCP, Bitcoin Core jest osiągany przez wewnętrzny mostek sieciowy z uwierzytelnianiem cookie, a ustawienia przeniesiono do akcji Konfiguruj. Istniejące ustawienia, historia bloków i certyfikat TLS są migrowane automatycznie.',
fr_FR:
'Ajoute des ports stratum et stratum-TLS configurables dans laction Configurer, et corrige une interface Stratum en double laissée après un changement de port. Portage vers StartOS 0.4.0 : Stratum est exposé directement sur le LAN comme interface TCP, Bitcoin Core est atteint via le pont réseau interne avec authentification par cookie, et les réglages ont migré vers laction Configurer. Les réglages existants, lhistorique des blocs et le certificat TLS sont migrés automatiquement.',
'Les ports stratum configurables ne définissent plus que le port externe (côté réseau). Les ports internes au conteneur sont fixes : changer un port ne laisse plus dinterface Stratum en double et ne redémarre plus le pool — les mineurs connectés le restent. Portage vers StartOS 0.4.0 : Stratum est exposé directement sur le LAN comme interface TCP, Bitcoin Core est atteint via le pont réseau interne avec authentification par cookie, et les réglages ont migré vers laction Configurer. Les réglages existants, lhistorique des blocs et le certificat TLS sont migrés automatiquement.',
},
migrations: {
up: async ({ effects }) => {