Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
23 changes: 22 additions & 1 deletion .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
22 changes: 22 additions & 0 deletions packages/dashmate/docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,25 @@ dashmate config get <option>
# Enable debug logging
dashmate config set core.log.debug.enabled true
```

## Running Dashmate commands concurrently

Dashmate keeps all configuration in a single `config.json` inside its home directory
(`~/.dashmate` by default).

Commands that change a configuration option — `dashmate config set` and friends — read,
change and save that file as one locked step. Two of them running at once cannot lose each
other's work: if one sets a Core RPC port while another pins a Drive image, both settings
survive. A command that has to wait for the lock waits milliseconds, since the lock is only
held for a read and a write.

Read-only commands such as `dashmate config get`, `dashmate status` and `dashmate core cli`
never write configuration at all, so they are always safe to run alongside anything else.

### Limits

Long-running commands that reconfigure a node — `dashmate setup`, `dashmate reset` and
`dashmate ssl obtain` — still load the configuration when they start and save it when they
finish. Do not change configuration with `dashmate config set` while one of those is
running: the long-running command saves the state it loaded, and an option changed in the
meantime is lost.
4 changes: 3 additions & 1 deletion packages/dashmate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,16 @@
"node-graceful": "^3.0.1",
"pretty-bytes": "^5.3.0",
"pretty-ms": "^7.0.0",
"proper-lockfile": "^4.1.2",
"public-ip": "^6.0.1",
"qs": "^6.14.2",
"rxjs": "^6.6.7",
"semver": "^7.5.3",
"systeminformation": "^5.31.1",
"table": "^6.8.1",
"tar": "7.5.10",
"wrap-ansi": "^7.0.0"
"wrap-ansi": "^7.0.0",
"write-file-atomic": "^5.0.1"
},
"devDependencies": {
"@babel/core": "^7.26.10",
Expand Down
9 changes: 6 additions & 3 deletions packages/dashmate/scripts/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ async function removeOrphanedSslContainers(docker) {

// Persist config if it was migrated
if (configFile.isChanged()) {
// Captured before the write, which clears these flags once the new state is
// on disk.
const changedConfigs = configFile.getAllConfigs()
.filter((config) => config.isChanged());

await configFileRepository.write(configFile);

configFile.getAllConfigs()
.filter((config) => config.isChanged())
.forEach(writeConfigTemplates);
changedConfigs.forEach(writeConfigTemplates);
}

const config = configFile.getConfig(configName);
Expand Down
12 changes: 11 additions & 1 deletion packages/dashmate/src/commands/config/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,18 @@ export default class ConfigCreateCommand extends BaseCommand {
},
flags,
configFile,
configFileRepository,
writeConfigTemplates,
) {
configFile.createConfig(configName, fromConfigName);
// Read, change and save in one locked step, so a config created here cannot
// revert a change another command saved in the meantime.
const freshConfigFile = configFileRepository.update((updatedConfigFile) => {
updatedConfigFile.createConfig(configName, fromConfigName);
});

// The new config needs its service files rendered, and the config file this
// command was handed at startup is deliberately not the one that changed.
writeConfigTemplates(freshConfigFile.getConfig(configName));

// eslint-disable-next-line no-console
console.log(`${configName} created`);
Expand Down
7 changes: 6 additions & 1 deletion packages/dashmate/src/commands/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ Shows default config name or sets another config as default
},
flags,
configFile,
configFileRepository,
) {
if (configName === null) {
// eslint-disable-next-line no-console
console.log(configFile.getDefaultConfigName());
} else {
configFile.setDefaultConfigName(configName);
// Read, change and save in one locked step, so pointing the default at a
// config cannot revert a change another command saved in the meantime.
configFileRepository.update((freshConfigFile) => {
freshConfigFile.setDefaultConfigName(configName);
});

// eslint-disable-next-line no-console
console.log(`${configName} config set as default`);
Expand Down
11 changes: 10 additions & 1 deletion packages/dashmate/src/commands/config/remove.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,24 @@ export default class ConfigRemoveCommand extends BaseCommand {
configFile,
defaultConfigs,
homeDir,
configFileRepository,
) {
if (defaultConfigs.has(configName)) {
throw new Error(`system config ${configName} can't be removed.\nPlease use 'dashmate reset --hard --config=${configName}' command to reset the configuration`);
}

const serviceConfigsPath = resolveConfigDirectory(homeDir, configName);

configFile.removeConfig(configName);
// Read, change and save in one locked step. Removing from the state loaded
// at startup would revert anything another command saved in the meantime,
// and removing a config another command already removed now fails here
// instead of writing.
configFileRepository.update((freshConfigFile) => {
freshConfigFile.removeConfig(configName);
});

// Only once the removal is saved. Deleting first would leave the service
// files gone while config.json still listed the config if saving failed.
fs.rmSync(serviceConfigsPath, {
recursive: true,
force: true,
Expand Down
15 changes: 14 additions & 1 deletion packages/dashmate/src/commands/config/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Sets a configuration option in the default config
},
flags,
config,
configFileRepository,
writeConfigTemplates,
) {
// Validate the path against the schema, not against the currently-set
// value. `config.get(...)` would throw `InvalidOptionPathError` for any
Expand All @@ -59,7 +61,18 @@ Sets a configuration option in the default config
value = optionValue;
}

config.set(optionPath, value);
// Read, change and save in one locked step, against the config name resolved
// for this command rather than re-resolving the default, which another
// process may have changed. Mutating the copy loaded at startup and saving it
// on exit would write a snapshot that is already out of date, reverting
// anything saved in between.
const configName = config.getName();

const configFile = configFileRepository.update((freshConfigFile) => {
freshConfigFile.getConfig(configName).set(optionPath, value);
});

writeConfigTemplates(configFile.getConfig(configName));

// eslint-disable-next-line no-console
console.log(`${optionPath} set to ${optionValue}`);
Expand Down
7 changes: 6 additions & 1 deletion packages/dashmate/src/commands/group/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ Shows default group name or sets another group as default
},
flags,
configFile,
configFileRepository,
) {
if (groupName === null) {
// eslint-disable-next-line no-console
console.log(configFile.getDefaultGroupName());
} else {
configFile.setDefaultGroupName(groupName);
// Read, change and save in one locked step, so pointing the default at a
// group cannot revert a change another command saved in the meantime.
configFileRepository.update((freshConfigFile) => {
freshConfigFile.setDefaultGroupName(groupName);
});

// eslint-disable-next-line no-console
console.log(`${groupName} group set as default`);
Expand Down
8 changes: 7 additions & 1 deletion packages/dashmate/src/config/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,15 @@ export default class Config {
assertSafeConfigName(name);

this.name = name;
this.changed = false;

this.setOptions(options, skipValidation);

// Hydration is not a mutation. setOptions() marks the config changed because
// it is a genuine edit when called on an existing config, but a config that
// was just loaded has nothing unsaved. Callers that build a config which must
// reach disk - the default set for a new config file, createConfig() - mark it
// changed themselves.
this.changed = false;
}

/**
Expand Down
Loading
Loading