Skip to content
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a00c5ac
chore: update gRPC queries cache [skip ci]
github-actions[bot] Jun 4, 2026
37ea011
Merge remote-tracking branch 'origin/v4.1-dev'
shumkov Jul 27, 2026
cc03079
Merge remote-tracking branch 'origin/v4.2-dev' into ssl
shumkov Aug 20, 2026
29276af
feat(dashmate): detect whether a session can answer a prompt
shumkov Aug 21, 2026
4a525bd
feat(dashmate): judge the certificate installed for the gateway
shumkov Aug 21, 2026
9b2b710
feat(dashmate): stop asking for an email, retry port 80, never prompt…
shumkov Aug 21, 2026
e2ee5a4
feat(dashmate): check the gateway certificate on update
shumkov Aug 21, 2026
51d0d18
fix(dashmate): report certificate problems doctor could not see
shumkov Aug 21, 2026
e92c4fd
test(dashmate): prove contactless issuance against a real ACME server
shumkov Aug 21, 2026
73bbc40
fix(dashmate): honour the JSON contract and stop the preflight claimi…
shumkov Aug 21, 2026
d6ec6af
refactor(dashmate): one rule for reading a boolean environment variable
shumkov Aug 21, 2026
01fcd60
fix(dashmate): stop warning about ZeroSSL after switching away from it
shumkov Aug 21, 2026
2d06357
style(dashmate): drop lint noise this change introduced
shumkov Aug 21, 2026
1a07767
fix(dashmate): five defects found by cross-model review
shumkov Aug 21, 2026
77d81d6
fix(dashmate): honest operator messaging and a private doctor archive
shumkov Aug 21, 2026
ce0095c
test(dashmate): pin the interactivity guards distinctly from the prom…
shumkov Aug 21, 2026
b4861d3
fix(dashmate): make the preflight decline rather than migrate unlocked
shumkov Aug 21, 2026
09f9709
fix(dashmate): reserve the migration error for a version that is actu…
shumkov Aug 21, 2026
6dfc75a
fix(dashmate): mask the operator's name even when the environment omi…
shumkov Aug 21, 2026
3c6bad7
fix(dashmate): let the preflight judge a config it would have to migrate
shumkov Aug 21, 2026
4ab84e4
fix(dashmate): tell a port conflict apart from a firewall, and say po…
shumkov Aug 21, 2026
ce2b710
fix(dashmate): stop offering a switch to the provider the node alread…
shumkov Aug 21, 2026
7ec0724
fix(dashmate): stop doctor advising a restart that would take a node …
shumkov Aug 21, 2026
51508ec
fix(dashmate): stop redaction blanking dashmate out of its own messages
shumkov Aug 21, 2026
3dbadce
fix(dashmate): judge and migrate the configuration from one snapshot
shumkov Aug 21, 2026
9a080b4
fix(dashmate): state the port 80 argument once, and test what is rend…
shumkov Aug 21, 2026
657529d
fix(dashmate): separate what the authority said from what it was neve…
shumkov Aug 21, 2026
2c37c63
fix(dashmate): compare the two certificates on the expired branch too
shumkov Aug 21, 2026
86eb80c
fix(dashmate): rewrite the home path only where it ends
shumkov Aug 21, 2026
ac759a0
fix(dashmate): stop retrying an issuance that already succeeded
shumkov Aug 21, 2026
50fb902
fix(dashmate): advise a restart only when the disk copy can actually …
shumkov Aug 21, 2026
c920176
fix(dashmate): require proof the disk pair is sound before advising a…
shumkov Aug 21, 2026
8d1edec
test(dashmate): pin that the verdict and the wire sample describe one…
shumkov Aug 21, 2026
1371288
fix(dashmate): say what failed the check, and stop tests inheriting a…
shumkov Aug 21, 2026
0623347
fix(dashmate): drop the removed Commit override for prerelease configs
shumkov Aug 21, 2026
7d83d1d
fix(dashmate): stop prescribing a fix that cannot work for a hijacked…
shumkov Aug 21, 2026
2b33981
fix(dashmate): require the key's certificate to be the bundle's first…
shumkov Aug 21, 2026
3aceeeb
fix(dashmate): match the node address against the SAN only
shumkov Aug 21, 2026
7e01230
fix(dashmate): reject a certificate whose validity has not started
shumkov Aug 21, 2026
5d5b444
fix(dashmate): stop telling operators to restart after obtaining a ce…
shumkov Aug 21, 2026
f4cb3dc
style(dashmate): flatten the identity branch and unshadow its detail
shumkov Aug 21, 2026
8d29701
fix(dashmate): stop claiming a failed attempt changed nothing
shumkov Aug 21, 2026
5140e70
style(dashmate): name the migration specs' local version binding in c…
shumkov Aug 21, 2026
adf6fb0
fix(dashmate): offer the ZeroSSL switch when the certificate is nearl…
shumkov Aug 21, 2026
e9f517c
fix(dashmate): reject a bundle whose certificate blocks are truncated
shumkov Aug 21, 2026
f85256c
fix(dashmate): stop the switch offer asserting things the verdict doe…
shumkov Aug 21, 2026
2a9df46
docs(dashmate): drop dev-time narration from the certificate test com…
shumkov Aug 21, 2026
7e80c1f
fix(dashmate): treat a PEM delimiter as a line, not as a substring
shumkov Aug 21, 2026
e3a270b
fix(dashmate): say only what the verdict established in the switch offer
shumkov Aug 21, 2026
e941198
docs(dashmate): state what the certificate comments protect, not how …
shumkov Aug 21, 2026
7805de2
fix(dashmate): accept delimiter lines padded with trailing whitespace
shumkov Aug 21, 2026
17cb102
fix(dashmate): stop advising a restart that reloads the stale certifi…
shumkov Aug 21, 2026
078e070
refactor(dashmate): tell an operator what to do, and check the certif…
shumkov Aug 22, 2026
7272f5c
refactor(dashmate): cut the certificate messages down to what to do
shumkov Aug 22, 2026
a564f0e
refactor(dashmate): stop reporting a missing contact address as a pro…
shumkov Aug 22, 2026
52efd33
feat(dashmate): warn that a certificate will become required to start…
shumkov Aug 22, 2026
3aa7cdd
fix(dashmate): print the repair command that actually repairs
shumkov Aug 23, 2026
50b1dd0
fix(dashmate): judge a certificate the same way wherever it is judged
shumkov Aug 23, 2026
4346d05
refactor(dashmate): tell an operator what is wrong, not which check n…
shumkov Aug 24, 2026
5cec821
fix(dashmate): prescribe one repair command per certificate
shumkov Aug 24, 2026
45d2442
fix(dashmate): do not pass a node whose address was never checked
shumkov Aug 24, 2026
9596d9d
fix(dashmate): set the address before prescribing a certificate
shumkov Aug 24, 2026
b3ed62c
fix(dashmate): ask for the address before reading the certificate
shumkov Aug 24, 2026
1736a37
fix(dashmate): prescribe repairs that can actually run
shumkov Aug 24, 2026
9e829ae
fix(dashmate): do not blame the authority when the dates are what failed
shumkov Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions packages/dashmate/configs/getConfigFileMigrationsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ import { stockImagePattern, historicalStockImagePattern } from '../src/config/st
* @param {DefaultConfigs} defaultConfigs
* @returns {getConfigFileMigrations}
*/
/**
* Migrations that move, copy or delete files, as opposed to reshaping data.
*
* Almost every migration only rewrites the configuration object, which can be
* applied in memory and discarded. These two relocate TLS material and remove
* the originals, so a caller that has promised to change nothing cannot run
* them and has to decline instead.
*
* A migration added here must be added to this set, and a test fails if one
* touches the filesystem without being declared.
*/
export const FILESYSTEM_MUTATING_MIGRATIONS = ['0.25.7', '1.0.0-dev.12'];

export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs) {
/**
* @typedef {function} getConfigFileMigrations
Expand Down Expand Up @@ -62,6 +75,27 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs)
}
}

/**
* Drop the Tenderdash Commit timeout overrides Tenderdash itself removed.
*
* The config schema stopped defining them, and it accepts no property it
* does not define, so a config that still carries them cannot be loaded at
* all.
*
* Called from more than one migration on purpose. A config records the
* version of the build that wrote it, and a development build records its
* own prerelease version - which semver orders above a key named after an
* earlier patch release. A config stamped that way skips such a key
* entirely, so a deletion the schema depends on has to be repeated at a key
* above every stamp still in the field. Repeating it costs nothing: removing
* a key that is not there does nothing.
*
* @param {Object} options - one config's options
*/
function dropRemovedTenderdashCommitOverride(options) {
delete options.platform?.drive?.tenderdash?.consensus?.unsafeOverride?.commit;
}

function getDefaultConfigByNetwork(network) {
if (network === NETWORK_MAINNET) {
return defaultConfigs.get('mainnet');
Expand Down Expand Up @@ -1702,11 +1736,12 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs)
return configFile;
},
'4.2.0': (configFile) => {
// The ACME directory certificates are requested from became
// configurable. Existing configs have no value for it, and the schema
// requires one, so fill in the directory they were already using.
Object.entries(configFile.configs)
.forEach(([, options]) => {
// Also done by the 4.1.1 migration, which a config written by a
// development build of this release is stamped above and skips.
dropRemovedTenderdashCommitOverride(options);

const providerConfigs = options.platform?.gateway?.ssl?.providerConfigs;

if (providerConfigs?.letsencrypt
Expand Down Expand Up @@ -1756,11 +1791,7 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs)
rsDapiDocker.image = base.get('platform.dapi.rsDapi.docker.image');
}

// The Commit timeout and BypassCommitTimeout overrides no longer
// exist in Tenderdash, which now only warns when they are set.
// Drop them: the config schema accepts no properties it does not
// define, so a config that kept them would fail validation.
delete options.platform?.drive?.tenderdash?.consensus?.unsafeOverride?.commit;
dropRemovedTenderdashCommitOverride(options);
});

return configFile;
Expand Down
56 changes: 44 additions & 12 deletions packages/dashmate/src/commands/ssl/obtain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Listr } from 'listr2';
import { Flags } from '@oclif/core';
import ServiceIsNotRunningError from '../../docker/errors/ServiceIsNotRunningError.js';
import ConfigBaseCommand from '../../oclif/command/ConfigBaseCommand.js';
import { PORT_80_PERMANENCE } from '../../listr/tasks/ssl/letsencrypt/obtainLetsEncryptCertificateTaskFactory.js';
import isInteractiveSession from '../../util/isInteractiveSession.js';
import MuteOneLineError from '../../oclif/errors/MuteOneLineError.js';
import Certificate from '../../ssl/zerossl/Certificate.js';
import LegoCertificate from '../../ssl/letsencrypt/LegoCertificate.js';
Expand Down Expand Up @@ -46,20 +48,22 @@ Certificate will be renewed if it is about to expire (see 'expiration-days' flag
*/
async runWithDependencies(
args,
{
verbose: isVerbose,
'no-retry': noRetry,
'expiration-days': expirationDaysFlag,
force,
provider: providerFlag,
},
flags,
config,
obtainZeroSSLCertificateTask,
obtainLetsEncryptCertificateTask,
configFileRepository,
configFile,
dockerCompose,
) {
const {
verbose: isVerbose,
'no-retry': noRetry,
'expiration-days': expirationDaysFlag,
force,
provider: providerFlag,
} = flags;

const provider = providerFlag || config.get('platform.gateway.ssl.provider');

let task;
Expand Down Expand Up @@ -109,6 +113,15 @@ Certificate will be renewed if it is about to expire (see 'expiration-days' flag
// been obtained by then, so failing there would report the whole
// command as failed and send the operator back to a provider that may
// have nothing left to issue.
//
// A signal is sufficient and nothing here needs to restart the
// container. PID 1 in the gateway container is Envoy's hot-restarter,
// not Envoy: its SIGHUP handler forks and re-execs Envoy with an
// incremented restart epoch against the same envoy.yaml. The new
// process parses that file from scratch and opens the certificate by
// name, so both a renewed certificate and a changed listener
// structure take effect while the old process drains. A container
// restart would achieve the same thing and cost an outage.
title: 'Reload gateway',
task: async (ctx, listrTask) => {
try {
Expand All @@ -135,14 +148,33 @@ Certificate will be renewed if it is about to expire (see 'expiration-days' flag
},
);

const context = {
noRetry,
force,
expirationDays,
// Whether the obtain may ask a question is decided here rather than
// inside the shared task, so a caller that never opts in - the helper's
// unattended renewal - cannot enable prompting by omission.
interactive: isInteractiveSession({ flags }),
};

try {
await tasks.run({
noRetry,
force,
expirationDays,
});
await tasks.run(context);
} catch (e) {
throw new MuteOneLineError(e);
} finally {
// Only when the gateway's certificate actually changed. This is the
// command the certificate check tells an operator to run, and an
// operator who opened port 80 for this one migration is the one who most
// needs to hear that it has to stay open - they never saw a failure that
// would have said so.
//
// Printed even when a later step fails: a certificate that was issued
// and then failed to install still counts against this node's limits,
// and the operator is about to close the port either way.
if (context.certificateObtained && provider === SSL_PROVIDERS.LETSENCRYPT) {
process.stderr.write(`\n${PORT_80_PERMANENCE}\n`);
}
}
}
}
Loading
Loading