cli: add `rad config schema` to the `rad-config` test

Adds the `rad config schema` to the `rad-config` test so that changes
to the schema will result in errors, forcing the implementor to ensure
the changes are correct.
This commit is contained in:
Fintan Halpenny 2026-02-23 18:04:29 +00:00 committed by Lorenz Leutgeb
parent f018b434cb
commit 6291cae592
1 changed files with 616 additions and 0 deletions

View File

@ -57,6 +57,622 @@ $ rad config
}
```
The `rad config schema` command provides the JSON schema that can be used to
validate the JSON of the user configuration.
```
$ rad config schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Config",
"description": "Local radicle configuration.",
"type": "object",
"properties": {
"publicExplorer": {
"description": "Public explorer. This is used for generating links.",
"$ref": "#/$defs/Explorer",
"default": "https://app.radicle.xyz/nodes/$host/$rid$path"
},
"preferredSeeds": {
"description": "Preferred seeds. These seeds will be used for explorer links/nand in other situations when a seed needs to be chosen.",
"type": "array",
"items": {
"$ref": "#/$defs/ConnectAddress"
},
"default": []
},
"web": {
"description": "Web configuration.",
"$ref": "#/$defs/WebConfig",
"default": {
"pinned": {
"repositories": []
}
}
},
"cli": {
"description": "CLI configuration.",
"$ref": "#/$defs/CliConfig",
"default": {
"hints": true
}
},
"node": {
"description": "Node configuration.",
"$ref": "#/$defs/NodeConfig"
}
},
"required": [
"node"
],
"$defs": {
"Explorer": {
"description": "A public explorer.",
"type": "string"
},
"ConnectAddress": {
"description": "A node address to connect to. Format: An Ed25519 public key in multibase encoding, followed by the symbol '@', followed by an IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', followed by a TCP port number.",
"type": "string",
"examples": [
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@rosa.radicle.xyz:8776",
"z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
"z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi@seed.example.com:8776",
"z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5@192.0.2.0:31337"
],
"pattern": "^.+@.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$"
},
"WebConfig": {
"description": "Web configuration.",
"type": "object",
"properties": {
"pinned": {
"description": "Pinned content.",
"$ref": "#/$defs/Pinned"
},
"bannerUrl": {
"description": "URL pointing to an image used in the header of a node page.",
"type": [
"string",
"null"
],
"format": "uri"
},
"avatarUrl": {
"description": "URL pointing to an image used as the node avatar.",
"type": [
"string",
"null"
],
"format": "uri"
},
"description": {
"description": "Node description.",
"type": [
"string",
"null"
],
"format": "uri"
}
},
"required": [
"pinned"
]
},
"Pinned": {
"description": "Pinned content. This can be used to pin certain content when/nlisting, e.g. pin repositories on a web client.",
"type": "object",
"properties": {
"repositories": {
"description": "Pinned repositories.",
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/$defs/RepoId"
}
}
},
"required": [
"repositories"
]
},
"RepoId": {
"description": "A repository identifier.",
"type": "string",
"examples": [
"rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5"
],
"minLength": 5,
"pattern": "rad:z[1-9a-km-zA-HJ-NP-Z]+"
},
"CliConfig": {
"description": "CLI configuration.",
"type": "object",
"properties": {
"hints": {
"description": "Whether to show hints or not in the CLI.",
"type": "boolean",
"default": false
}
}
},
"NodeConfig": {
"description": "Service configuration.",
"type": "object",
"properties": {
"alias": {
"description": "Node alias.",
"$ref": "#/$defs/Alias"
},
"listen": {
"description": "Socket address (a combination of IPv4 or IPv6 address and TCP port) to listen on.",
"type": "array",
"items": {
"type": "string"
},
"examples": [
"127.0.0.1:8776"
],
"default": []
},
"peers": {
"description": "Peer configuration.",
"$ref": "#/$defs/PeerConfig",
"default": {
"type": "dynamic"
}
},
"connect": {
"description": "Peers to connect to on startup./nConnections to these peers will be maintained.",
"type": "array",
"uniqueItems": true,
"items": {
"$ref": "#/$defs/ConnectAddress"
},
"default": []
},
"externalAddresses": {
"description": "Specify the node's public addresses",
"type": "array",
"items": {
"$ref": "#/$defs/Address"
},
"default": []
},
"proxy": {
"description": "Global proxy.",
"type": [
"string",
"null"
]
},
"onion": {
"description": "Onion address config.",
"anyOf": [
{
"$ref": "#/$defs/AddressConfig"
},
{
"type": "null"
}
]
},
"network": {
"description": "Peer-to-peer network.",
"$ref": "#/$defs/Network",
"default": "main"
},
"log": {
"description": "Log level.",
"$ref": "#/$defs/LogLevel",
"default": "INFO"
},
"relay": {
"description": "Whether or not our node should relay messages.",
"$ref": "#/$defs/Relay",
"default": "auto"
},
"limits": {
"description": "Configured service limits.",
"$ref": "#/$defs/Limits",
"default": {
"routingMaxSize": 1000,
"routingMaxAge": 604800,
"gossipMaxAge": 1209600,
"fetchConcurrency": 1,
"maxOpenFiles": 4096,
"rate": {
"inbound": {
"fillRate": 5.0,
"capacity": 1024
},
"outbound": {
"fillRate": 10.0,
"capacity": 2048
}
},
"connection": {
"inbound": 128,
"outbound": 16
},
"fetchPackReceive": "500.0 MiB"
}
},
"workers": {
"description": "Number of worker threads to spawn.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 8
},
"seedingPolicy": {
"description": "Default seeding policy.",
"$ref": "#/$defs/DefaultSeedingPolicy",
"default": {
"default": "block"
}
},
"secret": {
"description": "Path to a file containing an Ed25519 secret key, in OpenSSH format, i.e./nwith the `-----BEGIN OPENSSH PRIVATE KEY-----` header. The corresponding/npublic key will be used as the Node ID./n/nA decryption password cannot be configured, but passed at runtime via/nthe environment variable `RAD_PASSPHRASE`.",
"type": [
"string",
"null"
]
}
},
"required": [
"alias"
],
"additionalProperties": true
},
"Alias": {
"description": "Node alias, i.e. a short and memorable name for it.",
"type": "string"
},
"PeerConfig": {
"description": "Peer configuration.",
"oneOf": [
{
"description": "Static peer set. Connect to the configured peers and maintain the connections.",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "static"
}
},
"required": [
"type"
]
},
{
"description": "Dynamic peer set.",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "dynamic"
}
},
"required": [
"type"
]
}
]
},
"Address": {
"description": "An IP address, or a DNS name, or a Tor onion name, followed by the symbol ':', followed by a TCP port number.",
"type": "string",
"examples": [
"xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd.onion:8776",
"seed.example.com:8776",
"192.0.2.0:31337"
],
"pattern": "^.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$"
},
"AddressConfig": {
"description": "Proxy configuration.",
"oneOf": [
{
"description": "Proxy connections to this address type.",
"type": "object",
"properties": {
"address": {
"description": "Proxy address.",
"type": "string"
},
"mode": {
"type": "string",
"const": "proxy"
}
},
"required": [
"mode",
"address"
]
},
{
"description": "Forward address to the next layer. Either this is the global proxy,/nor the operating system, via DNS.",
"type": "object",
"properties": {
"mode": {
"type": "string",
"const": "forward"
}
},
"required": [
"mode"
]
}
]
},
"Network": {
"description": "Peer-to-peer network.",
"type": "string",
"enum": [
"main",
"test"
]
},
"LogLevel": {
"$ref": "#/$defs/Level"
},
"Level": {
"description": "A log level.",
"oneOf": [
{
"description": "Designates very serious errors.",
"type": "string",
"const": "ERROR"
},
{
"description": "Designates hazardous situations.",
"type": "string",
"const": "WARN"
},
{
"description": "Designates useful information.",
"type": "string",
"const": "INFO"
},
{
"description": "Designates lower priority information.",
"type": "string",
"const": "DEBUG"
},
{
"description": "Designates very low priority, often extremely verbose, information.",
"type": "string",
"const": "TRACE"
}
]
},
"Relay": {
"description": "Relay configuration.",
"oneOf": [
{
"description": "Always relay messages.",
"type": "string",
"const": "always"
},
{
"description": "Never relay messages.",
"type": "string",
"const": "never"
},
{
"description": "Relay messages when applicable.",
"type": "string",
"const": "auto"
}
]
},
"Limits": {
"description": "Configuration parameters defining attributes of minima and maxima.",
"type": "object",
"properties": {
"routingMaxSize": {
"description": "Number of routing table entries before we start pruning.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 1000
},
"routingMaxAge": {
"description": "How long to keep a routing table entry before being pruned.",
"$ref": "#/$defs/LocalDuration",
"default": 604800
},
"gossipMaxAge": {
"description": "How long to keep a gossip message entry before pruning it.",
"$ref": "#/$defs/LocalDuration",
"default": 1209600
},
"fetchConcurrency": {
"description": "Maximum number of concurrent fetches per peer connection.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 1
},
"maxOpenFiles": {
"description": "Maximum number of open files.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 4096
},
"rate": {
"description": "Rate limiter settings.",
"$ref": "#/$defs/RateLimits",
"default": {
"inbound": {
"fillRate": 5.0,
"capacity": 1024
},
"outbound": {
"fillRate": 10.0,
"capacity": 2048
}
}
},
"connection": {
"description": "Connection limits.",
"$ref": "#/$defs/ConnectionLimits",
"default": {
"inbound": 128,
"outbound": 16
}
},
"fetchPackReceive": {
"description": "Channel limits.",
"$ref": "#/$defs/FetchPackSizeLimit",
"default": "500.0 MiB"
}
}
},
"LocalDuration": {
"description": "A time duration measured locally in seconds.",
"type": "integer",
"format": "uint128",
"minimum": 0
},
"RateLimits": {
"description": "Rate limits for inbound and outbound connections.",
"type": "object",
"properties": {
"inbound": {
"$ref": "#/$defs/RateLimit"
},
"outbound": {
"$ref": "#/$defs/RateLimit"
}
},
"required": [
"inbound",
"outbound"
]
},
"RateLimit": {
"description": "Rate limits for a single connection.",
"type": "object",
"properties": {
"fillRate": {
"type": "number",
"format": "double"
},
"capacity": {
"type": "integer",
"format": "uint",
"minimum": 0
}
},
"required": [
"fillRate",
"capacity"
]
},
"ConnectionLimits": {
"description": "Connection limits.",
"type": "object",
"properties": {
"inbound": {
"description": "Max inbound connections.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 128
},
"outbound": {
"description": "Max outbound connections. Note that this can be higher than the *target* number.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 16
}
}
},
"FetchPackSizeLimit": {
"description": "Limiter for byte streams./n/nDefault: 500MiB",
"$ref": "#/$defs/ByteSize"
},
"ByteSize": {
"description": "Byte quantities using unit prefixes according to SI or ISO/IEC 80000-13.",
"type": "string",
"pattern": "^//d+(//.//d+)? ((K|M|G|T|P)i?B?|B)$",
"examples": [
"7 G",
"50.3 TiB",
"200 B",
"4 Ki",
"10 MB"
]
},
"DefaultSeedingPolicy": {
"description": "Default seeding policy. Applies when no repository policies for the given repo are found.",
"oneOf": [
{
"description": "Allow seeding.",
"type": "object",
"properties": {
"scope": {
"description": "Seeding scope.",
"$ref": "#/$defs/Scope"
},
"default": {
"type": "string",
"const": "allow"
}
},
"required": [
"default",
"scope"
]
},
{
"description": "Block seeding.",
"type": "object",
"properties": {
"default": {
"type": "string",
"const": "block"
}
},
"required": [
"default"
]
}
]
},
"Scope": {
"description": "[`Scope`] provides a schema for [`policy::Scope`], where the inner scope is/noptional. It is introduced to allow ease migration to a future/nversion of [`DefaultSeedingPolicy::Allow`], where no or different defaults/napply to [`DefaultSeedingPolicy::Allow::scope`].",
"anyOf": [
{
"$ref": "#/$defs/Scope2"
},
{
"type": "null"
}
]
},
"Scope2": {
"description": "Follow scope of a seeded repository.",
"oneOf": [
{
"description": "Seed remotes that are explicitly followed.",
"type": "string",
"const": "followed"
},
{
"description": "Seed all remotes.",
"type": "string",
"const": "all"
}
]
}
}
}
```
You can also get any value in the configuration by path, eg.
```