rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwooda0f8370a5211d2e096c53f56eda5eb3ed39105f9
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "1fe851582339876f5421e68bed27ed31fedef6a0",
"author": {
"id": "did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
"alias": "cloudhead"
},
"title": "node: Simplify configuration",
"state": {
"status": "merged",
"conflicts": []
},
"before": "db98daeccaa08d2dbf2982beec599519be24f6af",
"after": "a0f8370a5211d2e096c53f56eda5eb3ed39105f9",
"commits": [
"a0f8370a5211d2e096c53f56eda5eb3ed39105f9"
],
"target": "7e1cb406bb935c3de008d2171e8961f57b4336ea",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "1fe851582339876f5421e68bed27ed31fedef6a0",
"author": {
"id": "did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
"alias": "cloudhead"
},
"description": "This is a change to the `config.json` file which removes certain options\nthat are best not touched and changes some options to make them easier\nto work with.\n\n1. We change the default journaling mode to \"wal\" and remove the config\n option.\n2. We remove the option to set the peer connection \"target\", as it's not\n a good idea to set this to a different value, as it affects the\n network.\n3. We combine seeding policy & scope, since there's no need to set the\n scope when the policy is \"block\". In that case, we always want to\n block \"all\" remotes. The new policy configuration has the following\n schema:\n\n { seedingPolicy: { default: \"block\" | \"allow\", scope?: \"all\" | \"followed\" } }\n\n The \"scope\" key is not used when \"default\" is set to \"block\".\n\n4. We add an `extra` field to the node config for options that are not\n recognized. We use this to warn the user.",
"base": "db98daeccaa08d2dbf2982beec599519be24f6af",
"oid": "a0f8370a5211d2e096c53f56eda5eb3ed39105f9",
"timestamp": 1717268191
},
{
"id": "22676c8ec8baa592343fefde0e1275a00ca6155c",
"author": {
"id": "did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
"alias": "cloudhead"
},
"description": "Fix tests.",
"base": "cc7d0cf3633282ee0307ddbc20f7641c9deec52e",
"oid": "3ae7e305bd6d93f3e92167285e2fe00c4cdef50d",
"timestamp": 1717502957
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "a7cee553-1adb-434d-b5de-33c3fb460845"
},
"info_url": "https://cci.rad.levitte.org//a7cee553-1adb-434d-b5de-33c3fb460845.html"
}
Started at: 2026-09-09 01:43:03.366206+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/a7cee553-1adb-434d-b5de-33c3fb460845/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 39 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 1fe851582339876f5421e68bed27ed31fedef6a0
✓ Switched to branch patch/1fe8515 at revision 1fe8515
✓ Branch patch/1fe8515 setup to track rad/patches/1fe851582339876f5421e68bed27ed31fedef6a0
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout a0f8370a5211d2e096c53f56eda5eb3ed39105f9
HEAD is now at a0f8370a node: Simplify configuration
Exit code: 0
$ rad patch show 1fe851582339876f5421e68bed27ed31fedef6a0 -p
╭──────────────────────────────────────────────────────────────────────────────────╮
│ Title node: Simplify configuration │
│ Patch 1fe851582339876f5421e68bed27ed31fedef6a0 │
│ Author cloudhead z6MktaN…hzPZRZW │
│ Head a0f8370a5211d2e096c53f56eda5eb3ed39105f9 │
│ Base db98daeccaa08d2dbf2982beec599519be24f6af │
│ Target master │
│ Branches patch/1fe8515 │
│ Commits ahead 1, behind 1317 │
│ Status merged │
│ │
│ This is a change to the `config.json` file which removes certain options │
│ that are best not touched and changes some options to make them easier │
│ to work with. │
│ │
│ 1. We change the default journaling mode to "wal" and remove the config │
│ option. │
│ 2. We remove the option to set the peer connection "target", as it's not │
│ a good idea to set this to a different value, as it affects the │
│ network. │
│ 3. We combine seeding policy & scope, since there's no need to set the │
│ scope when the policy is "block". In that case, we always want to │
│ block "all" remotes. The new policy configuration has the following │
│ schema: │
│ │
│ { seedingPolicy: { default: "block" | "allow", scope?: "all" | "followed" } } │
│ │
│ The "scope" key is not used when "default" is set to "block". │
│ │
│ 4. We add an `extra` field to the node config for options that are not │
│ recognized. We use this to warn the user. │
├──────────────────────────────────────────────────────────────────────────────────┤
│ a0f8370 node: Simplify configuration │
├──────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 1fe8515 @ db98dae..a0f8370 by cloudhead z6MktaN…hzPZRZW 2 years ago │
│ ↑ Revision 22676c8 @ cc7d0cf..3ae7e30 by cloudhead z6MksFq…bS9wzpT 2 years ago │
│ └─ ✓ merged by cloudhead z6MksFq…bS9wzpT │
╰──────────────────────────────────────────────────────────────────────────────────╯
commit a0f8370a5211d2e096c53f56eda5eb3ed39105f9
Author: Alexis Sellier <self@cloudhead.io>
Date: Sat Jun 1 19:01:01 2024 +0200
node: Simplify configuration
This is a change to the `config.json` file which removes certain options
that are best not touched and changes some options to make them easier
to work with.
1. We change the default journaling mode to "wal" and remove the config
option.
2. We remove the option to set the peer connection "target", as it's not
a good idea to set this to a different value, as it affects the
network.
3. We combine seeding policy & scope, since there's no need to set the
scope when the policy is "block". In that case, we always want to
block "all" remotes. The new policy configuration has the following
schema:
{ seedingPolicy: { default: "block" | "allow", scope?: "all" | "followed" } }
The "scope" key is not used when "default" is set to "block".
4. We add an `extra` field to the node config for options that are not
recognized. We use this to warn the user.
diff --git a/radicle-cli/examples/rad-block.md b/radicle-cli/examples/rad-block.md
index d1d8503ca1..267229c7c0 100644
--- a/radicle-cli/examples/rad-block.md
+++ b/radicle-cli/examples/rad-block.md
@@ -35,9 +35,9 @@ And a 'block' policy was added:
```
$ rad seed
-╭──────────────────────────────────────────────────────────────╮
-│ Repository Name Policy Scope │
-├──────────────────────────────────────────────────────────────┤
-│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji block followed │
-╰──────────────────────────────────────────────────────────────╯
+╭───────────────────────────────────────────────────────────╮
+│ Repository Name Policy Scope │
+├───────────────────────────────────────────────────────────┤
+│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji block all │
+╰───────────────────────────────────────────────────────────╯
```
diff --git a/radicle-cli/examples/rad-config.md b/radicle-cli/examples/rad-config.md
index a8ed742b92..e47467da6e 100644
--- a/radicle-cli/examples/rad-config.md
+++ b/radicle-cli/examples/rad-config.md
@@ -21,14 +21,10 @@ $ rad config
"alias": "alice",
"listen": [],
"peers": {
- "type": "dynamic",
- "target": 8
+ "type": "dynamic"
},
"connect": [],
"externalAddresses": [],
- "db": {
- "journalMode": "rollback"
- },
"network": "main",
"log": "INFO",
"relay": "auto",
@@ -54,8 +50,9 @@ $ rad config
}
},
"workers": 8,
- "policy": "block",
- "scope": "all"
+ "seedingPolicy": {
+ "default": "block"
+ }
}
}
```
diff --git a/radicle-cli/src/commands/auth.rs b/radicle-cli/src/commands/auth.rs
index 38bfe3f9d0..fe13ac8848 100644
--- a/radicle-cli/src/commands/auth.rs
+++ b/radicle-cli/src/commands/auth.rs
@@ -169,6 +169,12 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
term::success!("Authenticated as {}", term::format::tertiary(profile.id()));
return Ok(());
}
+ for (key, _) in &profile.config.node.extra {
+ term::warning(format!(
+ "unused or deprecated configuration attribute {:?}",
+ key
+ ));
+ }
// If our key is encrypted, we try to authenticate with SSH Agent and
// register it; only if it is running.
diff --git a/radicle-cli/src/commands/inspect.rs b/radicle-cli/src/commands/inspect.rs
index 689ae9eca6..dabd81e6d4 100644
--- a/radicle-cli/src/commands/inspect.rs
+++ b/radicle-cli/src/commands/inspect.rs
@@ -9,7 +9,7 @@ use chrono::prelude::*;
use radicle::identity::RepoId;
use radicle::identity::{DocAt, Identity};
-use radicle::node::policy::Policy;
+use radicle::node::policy::SeedingPolicy;
use radicle::node::AliasStore as _;
use radicle::storage::git::{Repository, Storage};
use radicle::storage::refs::RefsAt;
@@ -175,15 +175,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let policies = profile.policies()?;
let seed = policies.seed_policy(&rid)?;
match seed.policy {
- Policy::Allow => {
+ SeedingPolicy::Allow { scope } => {
println!(
"Repository {} is {} with scope {}",
term::format::tertiary(&rid),
term::format::positive("being seeded"),
- term::format::dim(format!("`{}`", seed.scope))
+ term::format::dim(format!("`{scope}`"))
);
}
- Policy::Block => {
+ SeedingPolicy::Block => {
println!(
"Repository {} is {}",
term::format::tertiary(&rid),
diff --git a/radicle-cli/src/commands/seed.rs b/radicle-cli/src/commands/seed.rs
index 9c98b3223d..f95a31ef44 100644
--- a/radicle-cli/src/commands/seed.rs
+++ b/radicle-cli/src/commands/seed.rs
@@ -3,7 +3,7 @@ use std::ffi::OsString;
use anyhow::anyhow;
use radicle::node::policy;
-use radicle::node::policy::Scope;
+use radicle::node::policy::{Policy, Scope};
use radicle::node::Handle;
use radicle::{prelude::*, storage, Node};
use radicle_term::Element as _;
@@ -164,15 +164,15 @@ pub fn seeding(profile: &Profile) -> anyhow::Result<()> {
]);
t.divider();
- for policy::SeedPolicy { rid, scope, policy } in store.seed_policies()? {
+ for policy::SeedPolicy { rid, policy } in store.seed_policies()? {
let id = rid.to_string();
let name = storage
.repository(rid)
.map_err(storage::RepositoryError::from)
.and_then(|repo| repo.project().map(|proj| proj.name().to_string()))
.unwrap_or_default();
- let scope = scope.to_string();
- let policy = term::format::policy(&policy);
+ let scope = policy.scope().unwrap_or_default().to_string();
+ let policy = term::format::policy(&Policy::from(policy));
t.push([
term::format::tertiary(id),
diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs
index 2e77393e81..0877299069 100644
--- a/radicle-cli/tests/commands.rs
+++ b/radicle-cli/tests/commands.rs
@@ -16,7 +16,7 @@ use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository};
use radicle::test::fixtures;
use radicle_cli_test::TestFormula;
-use radicle_node::service::policy::{Policy, Scope};
+use radicle_node::service::policy::{Scope, SeedingPolicy};
use radicle_node::service::Event;
use radicle_node::test::environment::{Config, Environment, Node};
#[allow(unused_imports)]
@@ -1188,7 +1188,7 @@ fn rad_unseed() {
fn rad_block() {
let mut environment = Environment::new();
let alice = environment.node(Config {
- policy: Policy::Allow,
+ seeding_policy: SeedingPolicy::Allow { scope: Scope::All },
..Config::test(Alias::new("alice"))
});
let working = tempfile::tempdir().unwrap();
@@ -1491,7 +1491,7 @@ fn rad_init_sync_preferred() {
let mut environment = Environment::new();
let mut alice = environment
.node(Config {
- policy: Policy::Allow,
+ seeding_policy: SeedingPolicy::Allow { scope: Scope::All },
..Config::test(Alias::new("alice"))
})
.spawn();
@@ -1523,7 +1523,7 @@ fn rad_init_sync_timeout() {
let mut environment = Environment::new();
let mut alice = environment
.node(Config {
- policy: Policy::Block,
+ seeding_policy: SeedingPolicy::Block,
..Config::test(Alias::new("alice"))
})
.spawn();
@@ -1866,8 +1866,7 @@ fn test_replication_via_seed() {
let alice = environment.node(config::relay("alice"));
let bob = environment.node(config::relay("bob"));
let seed = environment.node(Config {
- policy: Policy::Allow,
- scope: Scope::All,
+ seeding_policy: SeedingPolicy::Allow { scope: Scope::All },
..config::relay("seed")
});
let working = environment.tmp().join("working");
@@ -2115,8 +2114,7 @@ fn rad_patch_open_explore() {
let mut environment = Environment::new();
let seed = environment
.node(Config {
- policy: Policy::Allow,
- scope: Scope::All,
+ seeding_policy: SeedingPolicy::Allow { scope: Scope::All },
..config::seed("seed")
})
.spawn();
diff --git a/radicle-fetch/src/policy.rs b/radicle-fetch/src/policy.rs
index 5cf7e7d605..508b5d65f2 100644
--- a/radicle-fetch/src/policy.rs
+++ b/radicle-fetch/src/policy.rs
@@ -5,7 +5,7 @@ use radicle::node::policy::config::Config;
use radicle::node::policy::store::Read;
use radicle::prelude::RepoId;
-pub use radicle::node::policy::{Policy, Scope};
+pub use radicle::node::policy::{Policy, Scope, SeedingPolicy};
#[derive(Clone, Debug)]
pub enum Allowed {
@@ -19,23 +19,23 @@ impl Allowed {
.seed_policy(&rid)
.map_err(|err| error::Policy::FailedPolicy { rid, err })?;
match entry.policy {
- Policy::Block => {
+ SeedingPolicy::Block => {
log::error!(target: "fetch", "Attempted to fetch non-seeded repo {rid}");
Err(error::Policy::BlockedPolicy { rid })
}
- Policy::Allow => match entry.scope {
- Scope::All => Ok(Self::All),
- Scope::Followed => {
- let nodes = config
- .follow_policies()
- .map_err(|err| error::Policy::FailedNodes { rid, err })?;
- let followed: HashSet<_> = nodes
- .filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
- .collect();
-
- Ok(Allowed::Followed { remotes: followed })
- }
- },
+ SeedingPolicy::Allow { scope: Scope::All } => Ok(Self::All),
+ SeedingPolicy::Allow {
+ scope: Scope::Followed,
+ } => {
+ let nodes = config
+ .follow_policies()
+ .map_err(|err| error::Policy::FailedNodes { rid, err })?;
+ let followed: HashSet<_> = nodes
+ .filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
+ .collect();
+
+ Ok(Allowed::Followed { remotes: followed })
+ }
}
}
}
diff --git a/radicle-httpd/src/api/v1/node.rs b/radicle-httpd/src/api/v1/node.rs
index 2e42b587d9..3e23823847 100644
--- a/radicle-httpd/src/api/v1/node.rs
+++ b/radicle-httpd/src/api/v1/node.rs
@@ -8,7 +8,10 @@ use serde_json::json;
use radicle::identity::RepoId;
use radicle::node::routing::Store;
-use radicle::node::{policy, AliasStore, Handle, NodeId, DEFAULT_TIMEOUT};
+use radicle::node::{
+ policy::{Policy, SeedPolicy},
+ AliasStore, Handle, NodeId, DEFAULT_TIMEOUT,
+};
use radicle::Node;
use crate::api::error::Error;
@@ -84,16 +87,11 @@ async fn node_policies_repos_handler(State(ctx): State<Context>) -> impl IntoRes
let policies = ctx.profile.policies()?;
let mut repos = Vec::new();
- for policy::SeedPolicy {
- rid: id,
- scope,
- policy,
- } in policies.seed_policies()?
- {
+ for SeedPolicy { rid: id, policy } in policies.seed_policies()? {
repos.push(json!({
"id": id,
- "scope": scope,
- "policy": policy,
+ "scope": policy.scope().unwrap_or_default(),
+ "policy": Policy::from(policy),
}));
}
diff --git a/radicle-node/src/runtime.rs b/radicle-node/src/runtime.rs
index 9f94a93a59..90cbb46083 100644
--- a/radicle-node/src/runtime.rs
+++ b/radicle-node/src/runtime.rs
@@ -119,16 +119,20 @@ impl Runtime {
let clock = LocalTime::now();
let timestamp = clock.into();
let storage = Storage::open(home.storage(), git::UserInfo { alias, key: id })?;
- let scope = config.scope;
- let policy = config.policy;
+ let policy = config.seeding_policy;
+ for (key, _) in &config.extra {
+ log::warn!(target: "node", "Unused or deprecated configuration attribute {:?}", key);
+ }
log::info!(target: "node", "Opening node database..");
- let db = home.database_mut()?.journal_mode(config.db.journal_mode)?;
+ let db = home
+ .database_mut()?
+ .journal_mode(node::db::JournalMode::default())?;
let mut stores: service::Stores<_> = db.clone().into();
log::info!(target: "node", "Opening policy database..");
let policies = home.policies_mut()?;
- let policies = policy::Config::new(policy, scope, policies);
+ let policies = policy::Config::new(policy, policies);
let notifications = home.notifications_mut()?;
let cobs_cache = cob::cache::Store::open(home.cobs().join(cob::cache::COBS_DB_FILE))?;
@@ -234,7 +238,6 @@ impl Runtime {
storage: storage.clone(),
fetch,
policy,
- scope,
policies_db: home.node().join(node::POLICIES_DB_FILE),
},
)?;
diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs
index dd6a0a20de..ea9c4fdd52 100644
--- a/radicle-node/src/service.rs
+++ b/radicle-node/src/service.rs
@@ -34,6 +34,7 @@ use radicle::node::seed::Store as _;
use radicle::node::{ConnectOptions, Penalty, Severity};
use radicle::storage::refs::SIGREFS_BRANCH;
use radicle::storage::RepositoryError;
+use radicle_fetch::policy::SeedingPolicy;
use crate::crypto;
use crate::crypto::{Signer, Verified};
@@ -49,7 +50,7 @@ use crate::service::gossip::Store as _;
use crate::service::message::{
Announcement, AnnouncementMessage, Info, NodeAnnouncement, Ping, RefsAnnouncement, RefsStatus,
};
-use crate::service::policy::{store::Write, Policy, Scope};
+use crate::service::policy::{store::Write, Scope};
use crate::storage;
use crate::storage::{refs::RefsAt, Namespaces, ReadStorage};
use crate::worker::fetch;
@@ -102,6 +103,8 @@ pub const MAX_RECONNECTION_DELTA: LocalDuration = LocalDuration::from_mins(60);
pub const CONNECTION_RETRY_DELTA: LocalDuration = LocalDuration::from_mins(10);
/// How long to wait for a fetch to stall before aborting, default is 3s.
pub const FETCH_TIMEOUT: time::Duration = time::Duration::from_secs(3);
+/// Target number of peers to maintain connections to.
+pub const TARGET_OUTBOUND_PEERS: usize = 8;
/// Maximum external address limit imposed by message size limits.
pub use message::ADDRESS_LIMIT;
@@ -560,7 +563,7 @@ where
self.filter = Filter::new(
self.policies
.seed_policies()?
- .filter_map(|t| (t.policy == Policy::Allow).then_some(t.rid)),
+ .filter_map(|t| (t.policy.is_allow()).then_some(t.rid)),
);
Ok(updated)
}
@@ -735,7 +738,7 @@ where
self.filter = Filter::new(
self.policies
.seed_policies()?
- .filter_map(|t| (t.policy == Policy::Allow).then_some(t.rid)),
+ .filter_map(|t| (t.policy.is_allow()).then_some(t.rid)),
);
// Connect to configured peers.
let addrs = self.config.connect.clone();
@@ -1216,9 +1219,12 @@ where
.policies
.seed_policy(&rid)
.expect("Service::dequeue_fetch: error accessing repo seeding configuration");
-
+ let SeedingPolicy::Allow { scope } = repo_entry.policy else {
+ debug!(target: "service", "Repository {rid} is no longer seeded, skipping..");
+ continue;
+ };
// Keep dequeueing if there was nothing to fetch, otherwise break.
- if self.fetch_refs_at(rid, from, refs, repo_entry.scope, timeout, channel) {
+ if self.fetch_refs_at(rid, from, refs, scope, timeout, channel) {
break;
}
} else {
@@ -1625,14 +1631,14 @@ where
let repo_entry = self.policies.seed_policy(&message.rid).expect(
"Service::handle_announcement: error accessing repo seeding configuration",
);
- if repo_entry.policy != Policy::Allow {
+ let SeedingPolicy::Allow { scope } = repo_entry.policy else {
debug!(
target: "service",
"Ignoring refs announcement from {announcer}: repository {} isn't seeded (t={timestamp})",
message.rid
);
return Ok(None);
- }
+ };
// Refs can be relayed by peers who don't have the data in storage,
// therefore we only check whether we are connected to the *announcer*,
// which is required by the protocol to only announce refs it has.
@@ -1645,14 +1651,8 @@ where
return Ok(relay);
};
// Finally, start the fetch.
- self.fetch_refs_at(
- message.rid,
- remote.id,
- refs,
- repo_entry.scope,
- FETCH_TIMEOUT,
- None,
- );
+ self.fetch_refs_at(message.rid, remote.id, refs, scope, FETCH_TIMEOUT, None);
+
return Ok(relay);
}
AnnouncementMessage::Node(
@@ -2202,7 +2202,7 @@ where
/// Return a new filter object, based on our seeding policy.
fn filter(&self) -> Filter {
- if self.config.policy == Policy::Allow {
+ if self.config.seeding_policy.is_allow() {
// TODO: Remove bits for blocked repos.
Filter::default()
} else {
@@ -2361,7 +2361,7 @@ where
let missing = self
.policies
.seed_policies()?
- .filter_map(|t| (t.policy == Policy::Allow).then_some(t.rid))
+ .filter_map(|t| (t.policy.is_allow()).then_some(t.rid))
.filter(|rid| !inventory.contains(rid));
for rid in missing {
@@ -2412,11 +2412,12 @@ where
/// Try to maintain a target number of connections.
fn maintain_connections(&mut self) {
- let PeerConfig::Dynamic { target } = self.config.peers else {
+ let PeerConfig::Dynamic = self.config.peers else {
return;
};
trace!(target: "service", "Maintaining connections..");
+ let target = TARGET_OUTBOUND_PEERS;
let now = self.clock;
let outbound = self
.sessions
diff --git a/radicle-node/src/test/peer.rs b/radicle-node/src/test/peer.rs
index 106d2ff68b..20e3dd103a 100644
--- a/radicle-node/src/test/peer.rs
+++ b/radicle-node/src/test/peer.rs
@@ -24,7 +24,7 @@ use crate::runtime::Emitter;
use crate::service;
use crate::service::io::Io;
use crate::service::message::*;
-use crate::service::policy::{Policy, Scope};
+use crate::service::policy::{Scope, SeedingPolicy};
use crate::service::*;
use crate::storage::git::transport::remote;
use crate::storage::Inventory;
@@ -101,8 +101,7 @@ pub struct Config<G: Signer + 'static> {
pub config: service::Config,
pub db: Stores<node::Database>,
pub local_time: LocalTime,
- pub policy: Policy,
- pub scope: Scope,
+ pub policy: SeedingPolicy,
pub signer: G,
pub rng: fastrand::Rng,
pub tmp: tempfile::TempDir,
@@ -121,8 +120,7 @@ impl Default for Config<MockSigner> {
config: service::Config::test(Alias::from_str("mocky").unwrap()),
db,
local_time: LocalTime::now(),
- policy: Policy::default(),
- scope: Scope::default(),
+ policy: SeedingPolicy::default(),
signer,
rng,
tmp,
@@ -162,7 +160,7 @@ where
mut config: Config<G>,
) -> Self {
let policies = policy::Store::<policy::store::Write>::memory().unwrap();
- let mut policies = policy::Config::new(config.policy, config.scope, policies);
+ let mut policies = policy::Config::new(config.policy, policies);
let id = *config.signer.public_key();
let ip = ip.into();
let local_addr = net::SocketAddr::new(ip, config.rng.u16(..));
diff --git a/radicle-node/src/worker.rs b/radicle-node/src/worker.rs
index eff250f995..ac3f5e3b0d 100644
--- a/radicle-node/src/worker.rs
+++ b/radicle-node/src/worker.rs
@@ -20,7 +20,7 @@ use radicle_fetch::FetchLimit;
use crate::runtime::{thread, Emitter, Handle};
use crate::service::policy;
-use crate::service::policy::Policy;
+use crate::service::policy::SeedingPolicy;
use crate::wire::StreamId;
pub use channels::{ChannelEvent, Channels};
@@ -34,9 +34,7 @@ pub struct Config {
/// Configuration for performing fetched.
pub fetch: FetchConfig,
/// Default policy, if a policy for a specific node or repository was not found.
- pub policy: Policy,
- /// Default scope, if a scope for a specific repository was not found.
- pub scope: policy::Scope,
+ pub policy: SeedingPolicy,
/// Path to the policies database.
pub policies_db: PathBuf,
}
@@ -284,7 +282,7 @@ impl Worker {
let policy = self.policies.seed_policy(&rid)?.policy;
let repo = self.storage.repository(rid)?;
let doc = repo.identity_doc()?;
- if !doc.is_visible_to(&remote) || policy == Policy::Block {
+ if !doc.is_visible_to(&remote) || policy.is_block() {
Err(UploadError::Unauthorized(remote, rid))
} else {
Ok(())
@@ -357,11 +355,8 @@ impl Pool {
) -> Result<Self, policy::Error> {
let mut pool = Vec::with_capacity(config.capacity);
for i in 0..config.capacity {
- let policies = policy::Config::new(
- config.policy,
- config.scope,
- policy::Store::reader(&config.policies_db)?,
- );
+ let policies =
+ policy::Config::new(config.policy, policy::Store::reader(&config.policies_db)?);
let worker = Worker {
nid,
tasks: tasks.clone(),
diff --git a/radicle/src/node/config.rs b/radicle/src/node/config.rs
index 68fb8da946..e51978af45 100644
--- a/radicle/src/node/config.rs
+++ b/radicle/src/node/config.rs
@@ -4,13 +4,12 @@ use std::ops::Deref;
use cyphernet::addr::PeerAddr;
use localtime::LocalDuration;
+use serde_json as json;
use crate::node;
-use crate::node::policy::{Policy, Scope};
-use crate::node::{db, Address, Alias, NodeId};
+use crate::node::policy::SeedingPolicy;
+use crate::node::{Address, Alias, NodeId};
-/// Target number of peers to maintain connections to.
-pub const TARGET_OUTBOUND_PEERS: usize = 8;
/// Default number of workers to spawn.
pub const DEFAULT_WORKERS: usize = 8;
@@ -134,8 +133,7 @@ impl Default for Limits {
pub struct ConnectionLimits {
/// Max inbound connections.
pub inbound: usize,
- /// Max outbound connections. Note that this is higher than the *target* number
- /// in [`TARGET_OUTBOUND_PEERS`].
+ /// Max outbound connections. Note that this can be higher than the *target* number.
pub outbound: usize,
}
@@ -223,14 +221,12 @@ pub enum PeerConfig {
/// Static peer set. Connect to the configured peers and maintain the connections.
Static,
/// Dynamic peer set.
- Dynamic { target: usize },
+ Dynamic,
}
impl Default for PeerConfig {
fn default() -> Self {
- Self::Dynamic {
- target: TARGET_OUTBOUND_PEERS,
- }
+ Self::Dynamic
}
}
@@ -261,14 +257,6 @@ pub enum AddressConfig {
Forward,
}
-/// Database configuration.
-#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct DbConfig {
- #[serde(default)]
- pub journal_mode: db::JournalMode,
-}
-
/// Service configuration.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -288,9 +276,6 @@ pub struct Config {
/// Specify the node's public addresses
#[serde(default)]
pub external_addresses: Vec<Address>,
- /// Database config.
- #[serde(default)]
- pub db: DbConfig,
/// Global proxy.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proxy: Option<net::SocketAddr>,
@@ -315,10 +300,10 @@ pub struct Config {
pub workers: usize,
/// Default seeding policy.
#[serde(default)]
- pub policy: Policy,
- /// Default seeding scope.
- #[serde(default)]
- pub scope: Scope,
+ pub seeding_policy: SeedingPolicy,
+ /// Extra fields that aren't supported.
+ #[serde(flatten, skip_serializing)]
+ pub extra: json::Map<String, json::Value>,
}
impl Config {
@@ -336,7 +321,6 @@ impl Config {
listen: vec![],
connect: HashSet::default(),
external_addresses: vec![],
- db: DbConfig::default(),
network: Network::default(),
proxy: None,
onion: None,
@@ -344,8 +328,8 @@ impl Config {
limits: Limits::default(),
workers: DEFAULT_WORKERS,
log: defaults::log(),
- policy: Policy::default(),
- scope: Scope::default(),
+ seeding_policy: SeedingPolicy::default(),
+ extra: json::Map::default(),
}
}
diff --git a/radicle/src/node/db.rs b/radicle/src/node/db.rs
index fc6aa77452..c5727477a4 100644
--- a/radicle/src/node/db.rs
+++ b/radicle/src/node/db.rs
@@ -48,10 +48,10 @@ pub enum Error {
pub enum JournalMode {
/// "WAL" mode. Good for concurrent reads & writes, but keeps some extra files around.
#[serde(rename = "wal")]
+ #[default]
WriteAheadLog,
/// Default "rollback" mode. Certain writes may block reads.
#[serde(alias = "rollback")]
- #[default]
Rollback,
}
diff --git a/radicle/src/node/policy.rs b/radicle/src/node/policy.rs
index 8befe8becb..f4b56d421c 100644
--- a/radicle/src/node/policy.rs
+++ b/radicle/src/node/policy.rs
@@ -15,8 +15,15 @@ pub use super::{Alias, NodeId};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SeedPolicy {
pub rid: RepoId,
- pub scope: Scope,
- pub policy: Policy,
+ pub policy: SeedingPolicy,
+}
+
+impl std::ops::Deref for SeedPolicy {
+ type Target = SeedingPolicy;
+
+ fn deref(&self) -> &Self::Target {
+ &self.policy
+ }
}
/// Node following policy.
@@ -27,6 +34,61 @@ pub struct FollowPolicy {
pub policy: Policy,
}
+/// Default seeding policy. Applies when no repository policies for the given repo are found.
+#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "camelCase", tag = "default")]
+pub enum SeedingPolicy {
+ /// Allow seeding.
+ Allow {
+ /// Seeding scope.
+ #[serde(default)]
+ scope: Scope,
+ },
+ /// Block seeding.
+ #[default]
+ Block,
+}
+
+impl SeedingPolicy {
+ /// Is this an "allow" policy.
+ pub fn is_allow(&self) -> bool {
+ matches!(self, Self::Allow { .. })
+ }
+
+ /// Is this a "block" policy.
+ pub fn is_block(&self) -> bool {
+ !self.is_allow()
+ }
+
+ /// Scope, if any.
+ pub fn scope(&self) -> Option<Scope> {
+ match self {
+ Self::Allow { scope } => Some(*scope),
+ Self::Block => None,
+ }
+ }
+}
+
+impl From<SeedingPolicy> for Policy {
+ fn from(p: SeedingPolicy) -> Self {
+ match p {
+ SeedingPolicy::Block => Policy::Block,
+ SeedingPolicy::Allow { .. } => Policy::Allow,
+ }
+ }
+}
+
+impl std::fmt::Display for SeedingPolicy {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{} ({})",
+ Policy::from(*self),
+ self.scope().unwrap_or_default()
+ )
+ }
+}
+
/// Resource policy.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
diff --git a/radicle/src/node/policy/config.rs b/radicle/src/node/policy/config.rs
index 9e690a862b..7a34a90e63 100644
--- a/radicle/src/node/policy/config.rs
+++ b/radicle/src/node/policy/config.rs
@@ -6,13 +6,13 @@ use log::error;
use thiserror::Error;
use crate::crypto::PublicKey;
-use crate::prelude::{NodeId, RepoId};
+use crate::prelude::RepoId;
use crate::storage::{Namespaces, ReadRepository as _, ReadStorage, RepositoryError};
pub use crate::node::policy::store;
pub use crate::node::policy::store::Error;
pub use crate::node::policy::store::Store;
-pub use crate::node::policy::{Alias, FollowPolicy, Policy, Scope, SeedPolicy};
+pub use crate::node::policy::{Alias, FollowPolicy, Policy, Scope, SeedPolicy, SeedingPolicy};
#[derive(Debug, Error)]
pub enum NamespacesError {
@@ -45,9 +45,7 @@ pub enum NamespacesError {
/// Policies configuration.
pub struct Config<T> {
/// Default policy, if a policy for a specific node or repository was not found.
- policy: Policy,
- /// Default scope, if a scope for a specific repository was not found.
- scope: Scope,
+ policy: SeedingPolicy,
/// Underlying configuration store.
store: Store<T>,
}
@@ -58,7 +56,6 @@ impl<T> fmt::Debug for Config<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Config")
.field("policy", &self.policy)
- .field("scope", &self.scope)
.field("store", &self.store)
.finish()
}
@@ -66,34 +63,13 @@ impl<T> fmt::Debug for Config<T> {
impl<T> Config<T> {
/// Create a new policy configuration.
- pub fn new(policy: Policy, scope: Scope, store: Store<T>) -> Self {
- Self {
- policy,
- scope,
- store,
- }
+ pub fn new(policy: SeedingPolicy, store: Store<T>) -> Self {
+ Self { policy, store }
}
/// Check if a repository is seeded.
pub fn is_seeding(&self, rid: &RepoId) -> Result<bool, Error> {
- self.seed_policy(rid)
- .map(|entry| entry.policy == Policy::Allow)
- }
-
- /// Check if a node is followed.
- pub fn is_following(&self, nid: &NodeId) -> Result<bool, Error> {
- self.follow_policy(nid)
- .map(|entry| entry.policy == Policy::Allow)
- }
-
- /// Get a node's following information.
- /// Returns the default policy if the node isn't found.
- pub fn follow_policy(&self, nid: &NodeId) -> Result<FollowPolicy, Error> {
- Ok(self.store.follow_policy(nid)?.unwrap_or(FollowPolicy {
- nid: *nid,
- alias: None,
- policy: self.policy,
- }))
+ self.seed_policy(rid).map(|entry| entry.policy.is_allow())
}
/// Get a repository's seeding information.
@@ -101,7 +77,6 @@ impl<T> Config<T> {
pub fn seed_policy(&self, rid: &RepoId) -> Result<SeedPolicy, Error> {
Ok(self.store.seed_policy(rid)?.unwrap_or(SeedPolicy {
rid: *rid,
- scope: self.scope,
policy: self.policy,
}))
}
@@ -120,37 +95,37 @@ impl<T> Config<T> {
.seed_policy(rid)
.map_err(|err| FailedPolicy { rid: *rid, err })?;
match entry.policy {
- Policy::Block => {
+ SeedingPolicy::Block => {
error!(target: "service", "Attempted to fetch untracked repo {rid}");
Err(NamespacesError::BlockedPolicy { rid: *rid })
}
- Policy::Allow => match entry.scope {
- Scope::All => Ok(Namespaces::All),
- Scope::Followed => {
- let nodes = self
- .follow_policies()
- .map_err(|err| FailedNodes { rid: *rid, err })?;
- let mut followed: HashSet<_> = nodes
- .filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
- .collect();
-
- if let Ok(repo) = storage.repository(*rid) {
- let delegates = repo
- .delegates()
- .map_err(|err| FailedDelegates { rid: *rid, err })?
- .map(PublicKey::from);
- followed.extend(delegates);
- };
- if followed.is_empty() {
- // Nb. returning All here because the
- // fetching logic will correctly determine
- // followed and delegate remotes.
- Ok(Namespaces::All)
- } else {
- Ok(Namespaces::Followed(followed))
- }
+ SeedingPolicy::Allow { scope: Scope::All } => Ok(Namespaces::All),
+ SeedingPolicy::Allow {
+ scope: Scope::Followed,
+ } => {
+ let nodes = self
+ .follow_policies()
+ .map_err(|err| FailedNodes { rid: *rid, err })?;
+ let mut followed: HashSet<_> = nodes
+ .filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
+ .collect();
+
+ if let Ok(repo) = storage.repository(*rid) {
+ let delegates = repo
+ .delegates()
+ .map_err(|err| FailedDelegates { rid: *rid, err })?
+ .map(PublicKey::from);
+ followed.extend(delegates);
+ };
+ if followed.is_empty() {
+ // Nb. returning All here because the
+ // fetching logic will correctly determine
+ // followed and delegate remotes.
+ Ok(Namespaces::All)
+ } else {
+ Ok(Namespaces::Followed(followed))
}
- },
+ }
}
}
}
diff --git a/radicle/src/node/policy/store.rs b/radicle/src/node/policy/store.rs
index 9a302aaf37..76352d3aa9 100644
--- a/radicle/src/node/policy/store.rs
+++ b/radicle/src/node/policy/store.rs
@@ -9,7 +9,7 @@ use thiserror::Error;
use crate::node::{Alias, AliasStore};
use crate::prelude::{NodeId, RepoId};
-use super::{FollowPolicy, Policy, Scope, SeedPolicy};
+use super::{FollowPolicy, Policy, Scope, SeedPolicy, SeedingPolicy};
/// How long to wait for the database lock to be released before failing a read.
const DB_READ_TIMEOUT: time::Duration = time::Duration::from_secs(3);
@@ -218,10 +218,8 @@ impl<T> Store<T> {
pub fn is_seeding(&self, id: &RepoId) -> Result<bool, Error> {
Ok(matches!(
self.seed_policy(id)?,
- Some(SeedPolicy {
- policy: Policy::Allow,
- ..
- })
+ Some(SeedPolicy { policy, .. })
+ if policy.is_allow()
))
}
@@ -260,11 +258,13 @@ impl<T> Store<T> {
stmt.bind((1, id))?;
if let Some(Ok(row)) = stmt.into_iter().next() {
- return Ok(Some(SeedPolicy {
- rid: *id,
- scope: row.read::<Scope, _>("scope"),
- policy: row.read::<Policy, _>("policy"),
- }));
+ let policy = match row.read::<Policy, _>("policy") {
+ Policy::Allow => SeedingPolicy::Allow {
+ scope: row.read::<Scope, _>("scope"),
+ },
+ Policy::Block => SeedingPolicy::Block,
+ };
+ return Ok(Some(SeedPolicy { rid: *id, policy }));
}
Ok(None)
}
@@ -307,14 +307,13 @@ impl<T> Store<T> {
while let Some(Ok(row)) = stmt.next() {
let id = row.read("id");
- let scope = row.read("scope");
- let policy = row.read::<Policy, _>("policy");
-
- entries.push(SeedPolicy {
- rid: id,
- scope,
- policy,
- });
+ let policy = match row.read::<Policy, _>("policy") {
+ Policy::Allow => SeedingPolicy::Allow {
+ scope: row.read::<Scope, _>("scope"),
+ },
+ Policy::Block => SeedingPolicy::Block,
+ };
+ entries.push(SeedPolicy { rid: id, policy });
}
Ok(Box::new(entries.into_iter()))
}
@@ -415,9 +414,15 @@ mod test {
let mut db = Store::open(":memory:").unwrap();
assert!(db.seed(&id, Scope::All).unwrap());
- assert_eq!(db.seed_policy(&id).unwrap().unwrap().scope, Scope::All);
+ assert_eq!(
+ db.seed_policy(&id).unwrap().unwrap().scope(),
+ Some(Scope::All)
+ );
assert!(db.seed(&id, Scope::Followed).unwrap());
- assert_eq!(db.seed_policy(&id).unwrap().unwrap().scope, Scope::Followed);
+ assert_eq!(
+ db.seed_policy(&id).unwrap().unwrap().scope(),
+ Some(Scope::Followed)
+ );
}
#[test]
@@ -426,9 +431,10 @@ mod test {
let mut db = Store::open(":memory:").unwrap();
assert!(db.seed(&id, Scope::All).unwrap());
- assert_eq!(db.seed_policy(&id).unwrap().unwrap().policy, Policy::Allow);
+ assert!(db.seed_policy(&id).unwrap().unwrap().is_allow());
assert!(db.set_seed_policy(&id, Policy::Block).unwrap());
- assert_eq!(db.seed_policy(&id).unwrap().unwrap().policy, Policy::Block);
+ assert!(!db.seed_policy(&id).unwrap().unwrap().is_allow());
+ assert_eq!(db.seed_policy(&id).unwrap().unwrap().scope(), None);
}
#[test]
diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs
index 2507641465..171375ca4e 100644
--- a/radicle/src/profile.rs
+++ b/radicle/src/profile.rs
@@ -15,6 +15,7 @@ use std::path::{Path, PathBuf};
use std::{fs, io};
use serde::Serialize;
+use serde_json as json;
use thiserror::Error;
use crate::crypto::ssh::agent::Agent;
@@ -22,13 +23,16 @@ use crate::crypto::ssh::{keystore, Keystore, Passphrase};
use crate::crypto::{PublicKey, Signer};
use crate::explorer::Explorer;
use crate::node::policy::config::store::Read;
-use crate::node::{notifications, policy, Alias, AliasStore};
-use crate::prelude::Did;
-use crate::prelude::NodeId;
+use crate::node::{
+ notifications, policy,
+ policy::{Policy, Scope, SeedingPolicy},
+ Alias, AliasStore,
+};
+use crate::prelude::{Did, NodeId};
use crate::storage::git::transport;
use crate::storage::git::Storage;
-use crate::storage::{self, ReadRepository};
-use crate::{cli, cob, git, node, web};
+use crate::storage::ReadRepository;
+use crate::{cli, cob, git, node, storage, web};
/// Environment variables used by radicle.
pub mod env {
@@ -229,12 +233,28 @@ impl Config {
/// Load a configuration from the given path.
pub fn load(path: &Path) -> Result<Self, ConfigError> {
- match fs::File::open(path) {
+ let mut cfg: Self = match fs::File::open(path) {
Ok(cfg) => {
- serde_json::from_reader(cfg).map_err(|e| ConfigError::Load(path.to_path_buf(), e))
+ json::from_reader(cfg).map_err(|e| ConfigError::Load(path.to_path_buf(), e))?
+ }
+ Err(e) => return Err(ConfigError::Io(path.to_path_buf(), e)),
+ };
+
+ // Handle deprecated policy configuration.
+ // Nb. This will override "seedingPolicy" if set! This code should be removed after 1.0.
+ if let (Some(p), Some(s)) = (cfg.node.extra.get("policy"), cfg.node.extra.get("scope")) {
+ if let (Ok(policy), Ok(scope)) = (
+ json::from_value::<Policy>(p.clone()),
+ json::from_value::<Scope>(s.clone()),
+ ) {
+ log::warn!(target: "radicle", "Overwriting `seedingPolicy` configuration");
+ cfg.node.seeding_policy = match policy {
+ Policy::Allow => SeedingPolicy::Allow { scope },
+ Policy::Block => SeedingPolicy::Block,
+ }
}
- Err(e) => Err(ConfigError::Io(path.to_path_buf(), e)),
}
+ Ok(cfg)
}
/// Write configuration to disk.
@@ -243,8 +263,8 @@ impl Config {
.create_new(true)
.write(true)
.open(path)?;
- let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
- let mut serializer = serde_json::Serializer::with_formatter(&file, formatter);
+ let formatter = json::ser::PrettyFormatter::with_indent(b" ");
+ let mut serializer = json::Serializer::with_formatter(&file, formatter);
self.serialize(&mut serializer)?;
file.write_all(b"\n")?;
@@ -315,7 +335,6 @@ impl Profile {
key: public_key,
},
)?;
-
transport::local::register(storage.clone());
Ok(Profile {
@@ -382,8 +401,7 @@ impl Profile {
pub fn policies(&self) -> Result<policy::config::Config<Read>, policy::store::Error> {
let path = self.node().join(node::POLICIES_DB_FILE);
let config = policy::config::Config::new(
- self.config.node.policy,
- self.config.node.scope,
+ self.config.node.seeding_policy,
policy::store::Store::reader(path)?,
);
Ok(config)
@@ -629,10 +647,9 @@ impl Home {
#[cfg(test)]
#[cfg(not(target_os = "macos"))]
mod test {
+ use super::*;
use std::fs;
- use super::Home;
-
// Checks that if we have:
// '/run/user/1000/.tmpqfK6ih/../.tmpqfK6ih/Radicle/Home'
//
@@ -656,4 +673,55 @@ mod test {
assert_eq!(home.path, path);
}
+
+ #[test]
+ fn test_config() {
+ let cfg = json::from_value::<Config>(json::json!({
+ "publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
+ "preferredSeeds": [],
+ "web": {
+ "pinned": {
+ "repositories": [
+ "rad:z3TajuiHXifEDEX4qbJxe8nXr9ufi",
+ "rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5"
+ ]
+ }
+ },
+ "cli": { "hints": true },
+ "node": {
+ "alias": "seed.radicle.xyz",
+ "listen": [],
+ "peers": { "type": "dynamic", "target": 8 },
+ "connect": [
+ "z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@ash.radicle.garden:8776",
+ "z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776"
+ ],
+ "externalAddresses": [ "seed.radicle.xyz:8776" ],
+ "db": { "journalMode": "wal" },
+ "network": "main",
+ "log": "INFO",
+ "relay": "always",
+ "limits": {
+ "routingMaxSize": 1000,
+ "routingMaxAge": 604800,
+ "gossipMaxAge": 604800,
+ "fetchConcurrency": 1,
+ "maxOpenFiles": 4096,
+ "rate": {
+ "inbound": { "fillRate": 10.0, "capacity": 2048 },
+ "outbound": { "fillRate": 10.0, "capacity": 2048 }
+ },
+ "connection": { "inbound": 128, "outbound": 16 }
+ },
+ "workers": 32,
+ "policy": "allow",
+ "scope": "all"
+ }
+ }))
+ .unwrap();
+
+ assert!(cfg.node.extra.contains_key("db"));
+ assert!(cfg.node.extra.contains_key("policy"));
+ assert!(cfg.node.extra.contains_key("scope"));
+ }
}
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny clippy::all cargo build --all-targets --workspace cargo doc --workspace cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name a7cee553-1adb-434d-b5de-33c3fb460845 -v /opt/radcis/ci.rad.levitte.org/cci/state/a7cee553-1adb-434d-b5de-33c3fb460845/s:/a7cee553-1adb-434d-b5de-33c3fb460845/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/a7cee553-1adb-434d-b5de-33c3fb460845/w:/a7cee553-1adb-434d-b5de-33c3fb460845/w -w /a7cee553-1adb-434d-b5de-33c3fb460845/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /a7cee553-1adb-434d-b5de-33c3fb460845/s/script.sh
+ cargo --version
info: syncing channel updates for '1.77-x86_64-unknown-linux-gnu'
info: latest update on 2024-04-09, rust version 1.77.2 (25ef9e3d8 2024-04-09)
info: downloading component 'cargo'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: installing component 'cargo'
info: installing component 'rust-std'
info: installing component 'rustc'
cargo 1.77.2 (e52e36006 2024-03-26)
+ rustc --version
rustc 1.77.2 (25ef9e3d8 2024-04-09)
+ cargo fmt --check
error: 'cargo-fmt' is not installed for the toolchain '1.77-x86_64-unknown-linux-gnu'.
To install, run `rustup component add --toolchain 1.77-x86_64-unknown-linux-gnu rustfmt`
Exit code: 1
{
"response": "finished",
"result": "failure"
}