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.
This commit is contained in:
parent
cc7d0cf363
commit
3ae7e305bd
|
|
@ -35,9 +35,9 @@ And a 'block' policy was added:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad seed
|
$ rad seed
|
||||||
╭──────────────────────────────────────────────────────────────╮
|
╭───────────────────────────────────────────────────────────╮
|
||||||
│ Repository Name Policy Scope │
|
│ Repository Name Policy Scope │
|
||||||
├──────────────────────────────────────────────────────────────┤
|
├───────────────────────────────────────────────────────────┤
|
||||||
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji block followed │
|
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji block all │
|
||||||
╰──────────────────────────────────────────────────────────────╯
|
╰───────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,10 @@ $ rad config
|
||||||
"alias": "alice",
|
"alias": "alice",
|
||||||
"listen": [],
|
"listen": [],
|
||||||
"peers": {
|
"peers": {
|
||||||
"type": "dynamic",
|
"type": "dynamic"
|
||||||
"target": 8
|
|
||||||
},
|
},
|
||||||
"connect": [],
|
"connect": [],
|
||||||
"externalAddresses": [],
|
"externalAddresses": [],
|
||||||
"db": {
|
|
||||||
"journalMode": "rollback"
|
|
||||||
},
|
|
||||||
"network": "main",
|
"network": "main",
|
||||||
"log": "INFO",
|
"log": "INFO",
|
||||||
"relay": "auto",
|
"relay": "auto",
|
||||||
|
|
@ -54,8 +50,9 @@ $ rad config
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workers": 8,
|
"workers": 8,
|
||||||
"policy": "block",
|
"seedingPolicy": {
|
||||||
"scope": "all"
|
"default": "block"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,12 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
||||||
term::success!("Authenticated as {}", term::format::tertiary(profile.id()));
|
term::success!("Authenticated as {}", term::format::tertiary(profile.id()));
|
||||||
return Ok(());
|
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
|
// If our key is encrypted, we try to authenticate with SSH Agent and
|
||||||
// register it; only if it is running.
|
// register it; only if it is running.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use chrono::prelude::*;
|
||||||
|
|
||||||
use radicle::identity::RepoId;
|
use radicle::identity::RepoId;
|
||||||
use radicle::identity::{DocAt, Identity};
|
use radicle::identity::{DocAt, Identity};
|
||||||
use radicle::node::policy::Policy;
|
use radicle::node::policy::SeedingPolicy;
|
||||||
use radicle::node::AliasStore as _;
|
use radicle::node::AliasStore as _;
|
||||||
use radicle::storage::git::{Repository, Storage};
|
use radicle::storage::git::{Repository, Storage};
|
||||||
use radicle::storage::refs::RefsAt;
|
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 policies = profile.policies()?;
|
||||||
let seed = policies.seed_policy(&rid)?;
|
let seed = policies.seed_policy(&rid)?;
|
||||||
match seed.policy {
|
match seed.policy {
|
||||||
Policy::Allow => {
|
SeedingPolicy::Allow { scope } => {
|
||||||
println!(
|
println!(
|
||||||
"Repository {} is {} with scope {}",
|
"Repository {} is {} with scope {}",
|
||||||
term::format::tertiary(&rid),
|
term::format::tertiary(&rid),
|
||||||
term::format::positive("being seeded"),
|
term::format::positive("being seeded"),
|
||||||
term::format::dim(format!("`{}`", seed.scope))
|
term::format::dim(format!("`{scope}`"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Policy::Block => {
|
SeedingPolicy::Block => {
|
||||||
println!(
|
println!(
|
||||||
"Repository {} is {}",
|
"Repository {} is {}",
|
||||||
term::format::tertiary(&rid),
|
term::format::tertiary(&rid),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::ffi::OsString;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::node::policy;
|
use radicle::node::policy;
|
||||||
use radicle::node::policy::Scope;
|
use radicle::node::policy::{Policy, Scope};
|
||||||
use radicle::node::Handle;
|
use radicle::node::Handle;
|
||||||
use radicle::{prelude::*, storage, Node};
|
use radicle::{prelude::*, storage, Node};
|
||||||
use radicle_term::Element as _;
|
use radicle_term::Element as _;
|
||||||
|
|
@ -164,15 +164,15 @@ pub fn seeding(profile: &Profile) -> anyhow::Result<()> {
|
||||||
]);
|
]);
|
||||||
t.divider();
|
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 id = rid.to_string();
|
||||||
let name = storage
|
let name = storage
|
||||||
.repository(rid)
|
.repository(rid)
|
||||||
.map_err(storage::RepositoryError::from)
|
.map_err(storage::RepositoryError::from)
|
||||||
.and_then(|repo| repo.project().map(|proj| proj.name().to_string()))
|
.and_then(|repo| repo.project().map(|proj| proj.name().to_string()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let scope = scope.to_string();
|
let scope = policy.scope().unwrap_or_default().to_string();
|
||||||
let policy = term::format::policy(&policy);
|
let policy = term::format::policy(&Policy::from(policy));
|
||||||
|
|
||||||
t.push([
|
t.push([
|
||||||
term::format::tertiary(id),
|
term::format::tertiary(id),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository};
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
|
|
||||||
use radicle_cli_test::TestFormula;
|
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::service::Event;
|
||||||
use radicle_node::test::environment::{Config, Environment, Node};
|
use radicle_node::test::environment::{Config, Environment, Node};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|
@ -1188,7 +1188,7 @@ fn rad_unseed() {
|
||||||
fn rad_block() {
|
fn rad_block() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
let alice = environment.node(Config {
|
let alice = environment.node(Config {
|
||||||
policy: Policy::Allow,
|
seeding_policy: SeedingPolicy::permissive(),
|
||||||
..Config::test(Alias::new("alice"))
|
..Config::test(Alias::new("alice"))
|
||||||
});
|
});
|
||||||
let working = tempfile::tempdir().unwrap();
|
let working = tempfile::tempdir().unwrap();
|
||||||
|
|
@ -1491,7 +1491,7 @@ fn rad_init_sync_preferred() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
let mut alice = environment
|
let mut alice = environment
|
||||||
.node(Config {
|
.node(Config {
|
||||||
policy: Policy::Allow,
|
seeding_policy: SeedingPolicy::permissive(),
|
||||||
..Config::test(Alias::new("alice"))
|
..Config::test(Alias::new("alice"))
|
||||||
})
|
})
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
@ -1523,7 +1523,7 @@ fn rad_init_sync_timeout() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
let mut alice = environment
|
let mut alice = environment
|
||||||
.node(Config {
|
.node(Config {
|
||||||
policy: Policy::Block,
|
seeding_policy: SeedingPolicy::Block,
|
||||||
..Config::test(Alias::new("alice"))
|
..Config::test(Alias::new("alice"))
|
||||||
})
|
})
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
@ -1866,8 +1866,7 @@ fn test_replication_via_seed() {
|
||||||
let alice = environment.node(config::relay("alice"));
|
let alice = environment.node(config::relay("alice"));
|
||||||
let bob = environment.node(config::relay("bob"));
|
let bob = environment.node(config::relay("bob"));
|
||||||
let seed = environment.node(Config {
|
let seed = environment.node(Config {
|
||||||
policy: Policy::Allow,
|
seeding_policy: SeedingPolicy::permissive(),
|
||||||
scope: Scope::All,
|
|
||||||
..config::relay("seed")
|
..config::relay("seed")
|
||||||
});
|
});
|
||||||
let working = environment.tmp().join("working");
|
let working = environment.tmp().join("working");
|
||||||
|
|
@ -2115,8 +2114,7 @@ fn rad_patch_open_explore() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
let seed = environment
|
let seed = environment
|
||||||
.node(Config {
|
.node(Config {
|
||||||
policy: Policy::Allow,
|
seeding_policy: SeedingPolicy::permissive(),
|
||||||
scope: Scope::All,
|
|
||||||
..config::seed("seed")
|
..config::seed("seed")
|
||||||
})
|
})
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use radicle::node::policy::config::Config;
|
||||||
use radicle::node::policy::store::Read;
|
use radicle::node::policy::store::Read;
|
||||||
use radicle::prelude::RepoId;
|
use radicle::prelude::RepoId;
|
||||||
|
|
||||||
pub use radicle::node::policy::{Policy, Scope};
|
pub use radicle::node::policy::{Policy, Scope, SeedingPolicy};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Allowed {
|
pub enum Allowed {
|
||||||
|
|
@ -19,23 +19,23 @@ impl Allowed {
|
||||||
.seed_policy(&rid)
|
.seed_policy(&rid)
|
||||||
.map_err(|err| error::Policy::FailedPolicy { rid, err })?;
|
.map_err(|err| error::Policy::FailedPolicy { rid, err })?;
|
||||||
match entry.policy {
|
match entry.policy {
|
||||||
Policy::Block => {
|
SeedingPolicy::Block => {
|
||||||
log::error!(target: "fetch", "Attempted to fetch non-seeded repo {rid}");
|
log::error!(target: "fetch", "Attempted to fetch non-seeded repo {rid}");
|
||||||
Err(error::Policy::BlockedPolicy { rid })
|
Err(error::Policy::BlockedPolicy { rid })
|
||||||
}
|
}
|
||||||
Policy::Allow => match entry.scope {
|
SeedingPolicy::Allow { scope: Scope::All } => Ok(Self::All),
|
||||||
Scope::All => Ok(Self::All),
|
SeedingPolicy::Allow {
|
||||||
Scope::Followed => {
|
scope: Scope::Followed,
|
||||||
let nodes = config
|
} => {
|
||||||
.follow_policies()
|
let nodes = config
|
||||||
.map_err(|err| error::Policy::FailedNodes { rid, err })?;
|
.follow_policies()
|
||||||
let followed: HashSet<_> = nodes
|
.map_err(|err| error::Policy::FailedNodes { rid, err })?;
|
||||||
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
let followed: HashSet<_> = nodes
|
||||||
.collect();
|
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(Allowed::Followed { remotes: followed })
|
Ok(Allowed::Followed { remotes: followed })
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ use serde_json::json;
|
||||||
|
|
||||||
use radicle::identity::RepoId;
|
use radicle::identity::RepoId;
|
||||||
use radicle::node::routing::Store;
|
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 radicle::Node;
|
||||||
|
|
||||||
use crate::api::error::Error;
|
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 policies = ctx.profile.policies()?;
|
||||||
let mut repos = Vec::new();
|
let mut repos = Vec::new();
|
||||||
|
|
||||||
for policy::SeedPolicy {
|
for SeedPolicy { rid: id, policy } in policies.seed_policies()? {
|
||||||
rid: id,
|
|
||||||
scope,
|
|
||||||
policy,
|
|
||||||
} in policies.seed_policies()?
|
|
||||||
{
|
|
||||||
repos.push(json!({
|
repos.push(json!({
|
||||||
"id": id,
|
"id": id,
|
||||||
"scope": scope,
|
"scope": policy.scope().unwrap_or_default(),
|
||||||
"policy": policy,
|
"policy": Policy::from(policy),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,13 +83,9 @@ mod routes {
|
||||||
"node": {
|
"node": {
|
||||||
"alias": "seed",
|
"alias": "seed",
|
||||||
"listen": [],
|
"listen": [],
|
||||||
"peers": {
|
"peers": { "type": "dynamic" },
|
||||||
"type": "dynamic",
|
|
||||||
"target": 8
|
|
||||||
},
|
|
||||||
"connect": [],
|
"connect": [],
|
||||||
"externalAddresses": [],
|
"externalAddresses": [],
|
||||||
"db": { "journalMode": "rollback" },
|
|
||||||
"network": "main",
|
"network": "main",
|
||||||
"log": "INFO",
|
"log": "INFO",
|
||||||
"relay": "auto",
|
"relay": "auto",
|
||||||
|
|
@ -115,8 +111,9 @@ mod routes {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workers": 8,
|
"workers": 8,
|
||||||
"policy": "block",
|
"seedingPolicy": {
|
||||||
"scope": "all"
|
"default": "block",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"home": seed.profile.path()
|
"home": seed.profile.path()
|
||||||
|
|
|
||||||
|
|
@ -119,16 +119,20 @@ impl Runtime {
|
||||||
let clock = LocalTime::now();
|
let clock = LocalTime::now();
|
||||||
let timestamp = clock.into();
|
let timestamp = clock.into();
|
||||||
let storage = Storage::open(home.storage(), git::UserInfo { alias, key: id })?;
|
let storage = Storage::open(home.storage(), git::UserInfo { alias, key: id })?;
|
||||||
let scope = config.scope;
|
let policy = config.seeding_policy;
|
||||||
let policy = config.policy;
|
|
||||||
|
|
||||||
|
for (key, _) in &config.extra {
|
||||||
|
log::warn!(target: "node", "Unused or deprecated configuration attribute {:?}", key);
|
||||||
|
}
|
||||||
log::info!(target: "node", "Opening node database..");
|
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();
|
let mut stores: service::Stores<_> = db.clone().into();
|
||||||
|
|
||||||
log::info!(target: "node", "Opening policy database..");
|
log::info!(target: "node", "Opening policy database..");
|
||||||
let policies = home.policies_mut()?;
|
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 notifications = home.notifications_mut()?;
|
||||||
let cobs_cache = cob::cache::Store::open(home.cobs().join(cob::cache::COBS_DB_FILE))?;
|
let cobs_cache = cob::cache::Store::open(home.cobs().join(cob::cache::COBS_DB_FILE))?;
|
||||||
|
|
||||||
|
|
@ -234,7 +238,6 @@ impl Runtime {
|
||||||
storage: storage.clone(),
|
storage: storage.clone(),
|
||||||
fetch,
|
fetch,
|
||||||
policy,
|
policy,
|
||||||
scope,
|
|
||||||
policies_db: home.node().join(node::POLICIES_DB_FILE),
|
policies_db: home.node().join(node::POLICIES_DB_FILE),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ use radicle::node::seed::Store as _;
|
||||||
use radicle::node::{ConnectOptions, Penalty, Severity};
|
use radicle::node::{ConnectOptions, Penalty, Severity};
|
||||||
use radicle::storage::refs::SIGREFS_BRANCH;
|
use radicle::storage::refs::SIGREFS_BRANCH;
|
||||||
use radicle::storage::RepositoryError;
|
use radicle::storage::RepositoryError;
|
||||||
|
use radicle_fetch::policy::SeedingPolicy;
|
||||||
|
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
use crate::crypto::{Signer, Verified};
|
use crate::crypto::{Signer, Verified};
|
||||||
|
|
@ -49,7 +50,7 @@ use crate::service::gossip::Store as _;
|
||||||
use crate::service::message::{
|
use crate::service::message::{
|
||||||
Announcement, AnnouncementMessage, Info, NodeAnnouncement, Ping, RefsAnnouncement, RefsStatus,
|
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;
|
||||||
use crate::storage::{refs::RefsAt, Namespaces, ReadStorage};
|
use crate::storage::{refs::RefsAt, Namespaces, ReadStorage};
|
||||||
use crate::worker::fetch;
|
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);
|
pub const CONNECTION_RETRY_DELTA: LocalDuration = LocalDuration::from_mins(10);
|
||||||
/// How long to wait for a fetch to stall before aborting, default is 3s.
|
/// 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);
|
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.
|
/// Maximum external address limit imposed by message size limits.
|
||||||
pub use message::ADDRESS_LIMIT;
|
pub use message::ADDRESS_LIMIT;
|
||||||
|
|
@ -560,7 +563,7 @@ where
|
||||||
self.filter = Filter::new(
|
self.filter = Filter::new(
|
||||||
self.policies
|
self.policies
|
||||||
.seed_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)
|
Ok(updated)
|
||||||
}
|
}
|
||||||
|
|
@ -735,7 +738,7 @@ where
|
||||||
self.filter = Filter::new(
|
self.filter = Filter::new(
|
||||||
self.policies
|
self.policies
|
||||||
.seed_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.
|
// Connect to configured peers.
|
||||||
let addrs = self.config.connect.clone();
|
let addrs = self.config.connect.clone();
|
||||||
|
|
@ -1216,9 +1219,12 @@ where
|
||||||
.policies
|
.policies
|
||||||
.seed_policy(&rid)
|
.seed_policy(&rid)
|
||||||
.expect("Service::dequeue_fetch: error accessing repo seeding configuration");
|
.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.
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1625,14 +1631,14 @@ where
|
||||||
let repo_entry = self.policies.seed_policy(&message.rid).expect(
|
let repo_entry = self.policies.seed_policy(&message.rid).expect(
|
||||||
"Service::handle_announcement: error accessing repo seeding configuration",
|
"Service::handle_announcement: error accessing repo seeding configuration",
|
||||||
);
|
);
|
||||||
if repo_entry.policy != Policy::Allow {
|
let SeedingPolicy::Allow { scope } = repo_entry.policy else {
|
||||||
debug!(
|
debug!(
|
||||||
target: "service",
|
target: "service",
|
||||||
"Ignoring refs announcement from {announcer}: repository {} isn't seeded (t={timestamp})",
|
"Ignoring refs announcement from {announcer}: repository {} isn't seeded (t={timestamp})",
|
||||||
message.rid
|
message.rid
|
||||||
);
|
);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
};
|
||||||
// Refs can be relayed by peers who don't have the data in storage,
|
// 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*,
|
// therefore we only check whether we are connected to the *announcer*,
|
||||||
// which is required by the protocol to only announce refs it has.
|
// which is required by the protocol to only announce refs it has.
|
||||||
|
|
@ -1645,14 +1651,8 @@ where
|
||||||
return Ok(relay);
|
return Ok(relay);
|
||||||
};
|
};
|
||||||
// Finally, start the fetch.
|
// Finally, start the fetch.
|
||||||
self.fetch_refs_at(
|
self.fetch_refs_at(message.rid, remote.id, refs, scope, FETCH_TIMEOUT, None);
|
||||||
message.rid,
|
|
||||||
remote.id,
|
|
||||||
refs,
|
|
||||||
repo_entry.scope,
|
|
||||||
FETCH_TIMEOUT,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
return Ok(relay);
|
return Ok(relay);
|
||||||
}
|
}
|
||||||
AnnouncementMessage::Node(
|
AnnouncementMessage::Node(
|
||||||
|
|
@ -2202,7 +2202,7 @@ where
|
||||||
|
|
||||||
/// Return a new filter object, based on our seeding policy.
|
/// Return a new filter object, based on our seeding policy.
|
||||||
fn filter(&self) -> Filter {
|
fn filter(&self) -> Filter {
|
||||||
if self.config.policy == Policy::Allow {
|
if self.config.seeding_policy.is_allow() {
|
||||||
// TODO: Remove bits for blocked repos.
|
// TODO: Remove bits for blocked repos.
|
||||||
Filter::default()
|
Filter::default()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2361,7 +2361,7 @@ where
|
||||||
let missing = self
|
let missing = self
|
||||||
.policies
|
.policies
|
||||||
.seed_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));
|
.filter(|rid| !inventory.contains(rid));
|
||||||
|
|
||||||
for rid in missing {
|
for rid in missing {
|
||||||
|
|
@ -2412,11 +2412,12 @@ where
|
||||||
|
|
||||||
/// Try to maintain a target number of connections.
|
/// Try to maintain a target number of connections.
|
||||||
fn maintain_connections(&mut self) {
|
fn maintain_connections(&mut self) {
|
||||||
let PeerConfig::Dynamic { target } = self.config.peers else {
|
let PeerConfig::Dynamic = self.config.peers else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
trace!(target: "service", "Maintaining connections..");
|
trace!(target: "service", "Maintaining connections..");
|
||||||
|
|
||||||
|
let target = TARGET_OUTBOUND_PEERS;
|
||||||
let now = self.clock;
|
let now = self.clock;
|
||||||
let outbound = self
|
let outbound = self
|
||||||
.sessions
|
.sessions
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ use crate::runtime::Emitter;
|
||||||
use crate::service;
|
use crate::service;
|
||||||
use crate::service::io::Io;
|
use crate::service::io::Io;
|
||||||
use crate::service::message::*;
|
use crate::service::message::*;
|
||||||
use crate::service::policy::{Policy, Scope};
|
use crate::service::policy::{Scope, SeedingPolicy};
|
||||||
use crate::service::*;
|
use crate::service::*;
|
||||||
use crate::storage::git::transport::remote;
|
use crate::storage::git::transport::remote;
|
||||||
use crate::storage::Inventory;
|
use crate::storage::Inventory;
|
||||||
|
|
@ -101,8 +101,7 @@ pub struct Config<G: Signer + 'static> {
|
||||||
pub config: service::Config,
|
pub config: service::Config,
|
||||||
pub db: Stores<node::Database>,
|
pub db: Stores<node::Database>,
|
||||||
pub local_time: LocalTime,
|
pub local_time: LocalTime,
|
||||||
pub policy: Policy,
|
pub policy: SeedingPolicy,
|
||||||
pub scope: Scope,
|
|
||||||
pub signer: G,
|
pub signer: G,
|
||||||
pub rng: fastrand::Rng,
|
pub rng: fastrand::Rng,
|
||||||
pub tmp: tempfile::TempDir,
|
pub tmp: tempfile::TempDir,
|
||||||
|
|
@ -121,8 +120,7 @@ impl Default for Config<MockSigner> {
|
||||||
config: service::Config::test(Alias::from_str("mocky").unwrap()),
|
config: service::Config::test(Alias::from_str("mocky").unwrap()),
|
||||||
db,
|
db,
|
||||||
local_time: LocalTime::now(),
|
local_time: LocalTime::now(),
|
||||||
policy: Policy::default(),
|
policy: SeedingPolicy::default(),
|
||||||
scope: Scope::default(),
|
|
||||||
signer,
|
signer,
|
||||||
rng,
|
rng,
|
||||||
tmp,
|
tmp,
|
||||||
|
|
@ -162,7 +160,7 @@ where
|
||||||
mut config: Config<G>,
|
mut config: Config<G>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let policies = policy::Store::<policy::store::Write>::memory().unwrap();
|
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 id = *config.signer.public_key();
|
||||||
let ip = ip.into();
|
let ip = ip.into();
|
||||||
let local_addr = net::SocketAddr::new(ip, config.rng.u16(..));
|
let local_addr = net::SocketAddr::new(ip, config.rng.u16(..));
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use radicle_fetch::FetchLimit;
|
||||||
|
|
||||||
use crate::runtime::{thread, Emitter, Handle};
|
use crate::runtime::{thread, Emitter, Handle};
|
||||||
use crate::service::policy;
|
use crate::service::policy;
|
||||||
use crate::service::policy::Policy;
|
use crate::service::policy::SeedingPolicy;
|
||||||
use crate::wire::StreamId;
|
use crate::wire::StreamId;
|
||||||
|
|
||||||
pub use channels::{ChannelEvent, Channels};
|
pub use channels::{ChannelEvent, Channels};
|
||||||
|
|
@ -34,9 +34,7 @@ pub struct Config {
|
||||||
/// Configuration for performing fetched.
|
/// Configuration for performing fetched.
|
||||||
pub fetch: FetchConfig,
|
pub fetch: FetchConfig,
|
||||||
/// Default policy, if a policy for a specific node or repository was not found.
|
/// Default policy, if a policy for a specific node or repository was not found.
|
||||||
pub policy: Policy,
|
pub policy: SeedingPolicy,
|
||||||
/// Default scope, if a scope for a specific repository was not found.
|
|
||||||
pub scope: policy::Scope,
|
|
||||||
/// Path to the policies database.
|
/// Path to the policies database.
|
||||||
pub policies_db: PathBuf,
|
pub policies_db: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +282,7 @@ impl Worker {
|
||||||
let policy = self.policies.seed_policy(&rid)?.policy;
|
let policy = self.policies.seed_policy(&rid)?.policy;
|
||||||
let repo = self.storage.repository(rid)?;
|
let repo = self.storage.repository(rid)?;
|
||||||
let doc = repo.identity_doc()?;
|
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))
|
Err(UploadError::Unauthorized(remote, rid))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -357,11 +355,8 @@ impl Pool {
|
||||||
) -> Result<Self, policy::Error> {
|
) -> Result<Self, policy::Error> {
|
||||||
let mut pool = Vec::with_capacity(config.capacity);
|
let mut pool = Vec::with_capacity(config.capacity);
|
||||||
for i in 0..config.capacity {
|
for i in 0..config.capacity {
|
||||||
let policies = policy::Config::new(
|
let policies =
|
||||||
config.policy,
|
policy::Config::new(config.policy, policy::Store::reader(&config.policies_db)?);
|
||||||
config.scope,
|
|
||||||
policy::Store::reader(&config.policies_db)?,
|
|
||||||
);
|
|
||||||
let worker = Worker {
|
let worker = Worker {
|
||||||
nid,
|
nid,
|
||||||
tasks: tasks.clone(),
|
tasks: tasks.clone(),
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@ use std::ops::Deref;
|
||||||
|
|
||||||
use cyphernet::addr::PeerAddr;
|
use cyphernet::addr::PeerAddr;
|
||||||
use localtime::LocalDuration;
|
use localtime::LocalDuration;
|
||||||
|
use serde_json as json;
|
||||||
|
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::node::policy::{Policy, Scope};
|
use crate::node::policy::SeedingPolicy;
|
||||||
use crate::node::{db, Address, Alias, NodeId};
|
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.
|
/// Default number of workers to spawn.
|
||||||
pub const DEFAULT_WORKERS: usize = 8;
|
pub const DEFAULT_WORKERS: usize = 8;
|
||||||
|
|
||||||
|
|
@ -134,8 +133,7 @@ impl Default for Limits {
|
||||||
pub struct ConnectionLimits {
|
pub struct ConnectionLimits {
|
||||||
/// Max inbound connections.
|
/// Max inbound connections.
|
||||||
pub inbound: usize,
|
pub inbound: usize,
|
||||||
/// Max outbound connections. Note that this is higher than the *target* number
|
/// Max outbound connections. Note that this can be higher than the *target* number.
|
||||||
/// in [`TARGET_OUTBOUND_PEERS`].
|
|
||||||
pub outbound: usize,
|
pub outbound: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,14 +221,12 @@ pub enum PeerConfig {
|
||||||
/// Static peer set. Connect to the configured peers and maintain the connections.
|
/// Static peer set. Connect to the configured peers and maintain the connections.
|
||||||
Static,
|
Static,
|
||||||
/// Dynamic peer set.
|
/// Dynamic peer set.
|
||||||
Dynamic { target: usize },
|
Dynamic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PeerConfig {
|
impl Default for PeerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Dynamic {
|
Self::Dynamic
|
||||||
target: TARGET_OUTBOUND_PEERS,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,14 +257,6 @@ pub enum AddressConfig {
|
||||||
Forward,
|
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.
|
/// Service configuration.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
@ -288,9 +276,6 @@ pub struct Config {
|
||||||
/// Specify the node's public addresses
|
/// Specify the node's public addresses
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub external_addresses: Vec<Address>,
|
pub external_addresses: Vec<Address>,
|
||||||
/// Database config.
|
|
||||||
#[serde(default)]
|
|
||||||
pub db: DbConfig,
|
|
||||||
/// Global proxy.
|
/// Global proxy.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub proxy: Option<net::SocketAddr>,
|
pub proxy: Option<net::SocketAddr>,
|
||||||
|
|
@ -315,10 +300,10 @@ pub struct Config {
|
||||||
pub workers: usize,
|
pub workers: usize,
|
||||||
/// Default seeding policy.
|
/// Default seeding policy.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub policy: Policy,
|
pub seeding_policy: SeedingPolicy,
|
||||||
/// Default seeding scope.
|
/// Extra fields that aren't supported.
|
||||||
#[serde(default)]
|
#[serde(flatten, skip_serializing)]
|
||||||
pub scope: Scope,
|
pub extra: json::Map<String, json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|
@ -336,7 +321,6 @@ impl Config {
|
||||||
listen: vec![],
|
listen: vec![],
|
||||||
connect: HashSet::default(),
|
connect: HashSet::default(),
|
||||||
external_addresses: vec![],
|
external_addresses: vec![],
|
||||||
db: DbConfig::default(),
|
|
||||||
network: Network::default(),
|
network: Network::default(),
|
||||||
proxy: None,
|
proxy: None,
|
||||||
onion: None,
|
onion: None,
|
||||||
|
|
@ -344,8 +328,8 @@ impl Config {
|
||||||
limits: Limits::default(),
|
limits: Limits::default(),
|
||||||
workers: DEFAULT_WORKERS,
|
workers: DEFAULT_WORKERS,
|
||||||
log: defaults::log(),
|
log: defaults::log(),
|
||||||
policy: Policy::default(),
|
seeding_policy: SeedingPolicy::default(),
|
||||||
scope: Scope::default(),
|
extra: json::Map::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@ pub enum Error {
|
||||||
pub enum JournalMode {
|
pub enum JournalMode {
|
||||||
/// "WAL" mode. Good for concurrent reads & writes, but keeps some extra files around.
|
/// "WAL" mode. Good for concurrent reads & writes, but keeps some extra files around.
|
||||||
#[serde(rename = "wal")]
|
#[serde(rename = "wal")]
|
||||||
|
#[default]
|
||||||
WriteAheadLog,
|
WriteAheadLog,
|
||||||
/// Default "rollback" mode. Certain writes may block reads.
|
/// Default "rollback" mode. Certain writes may block reads.
|
||||||
#[serde(alias = "rollback")]
|
#[serde(alias = "rollback")]
|
||||||
#[default]
|
|
||||||
Rollback,
|
Rollback,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,15 @@ pub use super::{Alias, NodeId};
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SeedPolicy {
|
pub struct SeedPolicy {
|
||||||
pub rid: RepoId,
|
pub rid: RepoId,
|
||||||
pub scope: Scope,
|
pub policy: SeedingPolicy,
|
||||||
pub policy: Policy,
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for SeedPolicy {
|
||||||
|
type Target = SeedingPolicy;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.policy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node following policy.
|
/// Node following policy.
|
||||||
|
|
@ -27,6 +34,66 @@ pub struct FollowPolicy {
|
||||||
pub policy: Policy,
|
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 {
|
||||||
|
/// Seed everything from anyone.
|
||||||
|
pub fn permissive() -> Self {
|
||||||
|
Self::Allow { scope: Scope::All }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Resource policy.
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ use log::error;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::crypto::PublicKey;
|
use crate::crypto::PublicKey;
|
||||||
use crate::prelude::{NodeId, RepoId};
|
use crate::prelude::RepoId;
|
||||||
use crate::storage::{Namespaces, ReadRepository as _, ReadStorage, RepositoryError};
|
use crate::storage::{Namespaces, ReadRepository as _, ReadStorage, RepositoryError};
|
||||||
|
|
||||||
pub use crate::node::policy::store;
|
pub use crate::node::policy::store;
|
||||||
pub use crate::node::policy::store::Error;
|
pub use crate::node::policy::store::Error;
|
||||||
pub use crate::node::policy::store::Store;
|
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)]
|
#[derive(Debug, Error)]
|
||||||
pub enum NamespacesError {
|
pub enum NamespacesError {
|
||||||
|
|
@ -45,9 +45,7 @@ pub enum NamespacesError {
|
||||||
/// Policies configuration.
|
/// Policies configuration.
|
||||||
pub struct Config<T> {
|
pub struct Config<T> {
|
||||||
/// Default policy, if a policy for a specific node or repository was not found.
|
/// Default policy, if a policy for a specific node or repository was not found.
|
||||||
policy: Policy,
|
policy: SeedingPolicy,
|
||||||
/// Default scope, if a scope for a specific repository was not found.
|
|
||||||
scope: Scope,
|
|
||||||
/// Underlying configuration store.
|
/// Underlying configuration store.
|
||||||
store: Store<T>,
|
store: Store<T>,
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +56,6 @@ impl<T> fmt::Debug for Config<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Config")
|
f.debug_struct("Config")
|
||||||
.field("policy", &self.policy)
|
.field("policy", &self.policy)
|
||||||
.field("scope", &self.scope)
|
|
||||||
.field("store", &self.store)
|
.field("store", &self.store)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
@ -66,34 +63,13 @@ impl<T> fmt::Debug for Config<T> {
|
||||||
|
|
||||||
impl<T> Config<T> {
|
impl<T> Config<T> {
|
||||||
/// Create a new policy configuration.
|
/// Create a new policy configuration.
|
||||||
pub fn new(policy: Policy, scope: Scope, store: Store<T>) -> Self {
|
pub fn new(policy: SeedingPolicy, store: Store<T>) -> Self {
|
||||||
Self {
|
Self { policy, store }
|
||||||
policy,
|
|
||||||
scope,
|
|
||||||
store,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a repository is seeded.
|
/// Check if a repository is seeded.
|
||||||
pub fn is_seeding(&self, rid: &RepoId) -> Result<bool, Error> {
|
pub fn is_seeding(&self, rid: &RepoId) -> Result<bool, Error> {
|
||||||
self.seed_policy(rid)
|
self.seed_policy(rid).map(|entry| entry.policy.is_allow())
|
||||||
.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,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a repository's seeding information.
|
/// Get a repository's seeding information.
|
||||||
|
|
@ -101,7 +77,6 @@ impl<T> Config<T> {
|
||||||
pub fn seed_policy(&self, rid: &RepoId) -> Result<SeedPolicy, Error> {
|
pub fn seed_policy(&self, rid: &RepoId) -> Result<SeedPolicy, Error> {
|
||||||
Ok(self.store.seed_policy(rid)?.unwrap_or(SeedPolicy {
|
Ok(self.store.seed_policy(rid)?.unwrap_or(SeedPolicy {
|
||||||
rid: *rid,
|
rid: *rid,
|
||||||
scope: self.scope,
|
|
||||||
policy: self.policy,
|
policy: self.policy,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -120,37 +95,37 @@ impl<T> Config<T> {
|
||||||
.seed_policy(rid)
|
.seed_policy(rid)
|
||||||
.map_err(|err| FailedPolicy { rid: *rid, err })?;
|
.map_err(|err| FailedPolicy { rid: *rid, err })?;
|
||||||
match entry.policy {
|
match entry.policy {
|
||||||
Policy::Block => {
|
SeedingPolicy::Block => {
|
||||||
error!(target: "service", "Attempted to fetch untracked repo {rid}");
|
error!(target: "service", "Attempted to fetch untracked repo {rid}");
|
||||||
Err(NamespacesError::BlockedPolicy { rid: *rid })
|
Err(NamespacesError::BlockedPolicy { rid: *rid })
|
||||||
}
|
}
|
||||||
Policy::Allow => match entry.scope {
|
SeedingPolicy::Allow { scope: Scope::All } => Ok(Namespaces::All),
|
||||||
Scope::All => Ok(Namespaces::All),
|
SeedingPolicy::Allow {
|
||||||
Scope::Followed => {
|
scope: Scope::Followed,
|
||||||
let nodes = self
|
} => {
|
||||||
.follow_policies()
|
let nodes = self
|
||||||
.map_err(|err| FailedNodes { rid: *rid, err })?;
|
.follow_policies()
|
||||||
let mut followed: HashSet<_> = nodes
|
.map_err(|err| FailedNodes { rid: *rid, err })?;
|
||||||
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
let mut followed: HashSet<_> = nodes
|
||||||
.collect();
|
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
||||||
|
.collect();
|
||||||
|
|
||||||
if let Ok(repo) = storage.repository(*rid) {
|
if let Ok(repo) = storage.repository(*rid) {
|
||||||
let delegates = repo
|
let delegates = repo
|
||||||
.delegates()
|
.delegates()
|
||||||
.map_err(|err| FailedDelegates { rid: *rid, err })?
|
.map_err(|err| FailedDelegates { rid: *rid, err })?
|
||||||
.map(PublicKey::from);
|
.map(PublicKey::from);
|
||||||
followed.extend(delegates);
|
followed.extend(delegates);
|
||||||
};
|
};
|
||||||
if followed.is_empty() {
|
if followed.is_empty() {
|
||||||
// Nb. returning All here because the
|
// Nb. returning All here because the
|
||||||
// fetching logic will correctly determine
|
// fetching logic will correctly determine
|
||||||
// followed and delegate remotes.
|
// followed and delegate remotes.
|
||||||
Ok(Namespaces::All)
|
Ok(Namespaces::All)
|
||||||
} else {
|
} else {
|
||||||
Ok(Namespaces::Followed(followed))
|
Ok(Namespaces::Followed(followed))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use thiserror::Error;
|
||||||
use crate::node::{Alias, AliasStore};
|
use crate::node::{Alias, AliasStore};
|
||||||
use crate::prelude::{NodeId, RepoId};
|
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.
|
/// 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);
|
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> {
|
pub fn is_seeding(&self, id: &RepoId) -> Result<bool, Error> {
|
||||||
Ok(matches!(
|
Ok(matches!(
|
||||||
self.seed_policy(id)?,
|
self.seed_policy(id)?,
|
||||||
Some(SeedPolicy {
|
Some(SeedPolicy { policy, .. })
|
||||||
policy: Policy::Allow,
|
if policy.is_allow()
|
||||||
..
|
|
||||||
})
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,11 +258,13 @@ impl<T> Store<T> {
|
||||||
stmt.bind((1, id))?;
|
stmt.bind((1, id))?;
|
||||||
|
|
||||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||||
return Ok(Some(SeedPolicy {
|
let policy = match row.read::<Policy, _>("policy") {
|
||||||
rid: *id,
|
Policy::Allow => SeedingPolicy::Allow {
|
||||||
scope: row.read::<Scope, _>("scope"),
|
scope: row.read::<Scope, _>("scope"),
|
||||||
policy: row.read::<Policy, _>("policy"),
|
},
|
||||||
}));
|
Policy::Block => SeedingPolicy::Block,
|
||||||
|
};
|
||||||
|
return Ok(Some(SeedPolicy { rid: *id, policy }));
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
@ -307,14 +307,13 @@ impl<T> Store<T> {
|
||||||
|
|
||||||
while let Some(Ok(row)) = stmt.next() {
|
while let Some(Ok(row)) = stmt.next() {
|
||||||
let id = row.read("id");
|
let id = row.read("id");
|
||||||
let scope = row.read("scope");
|
let policy = match row.read::<Policy, _>("policy") {
|
||||||
let policy = row.read::<Policy, _>("policy");
|
Policy::Allow => SeedingPolicy::Allow {
|
||||||
|
scope: row.read::<Scope, _>("scope"),
|
||||||
entries.push(SeedPolicy {
|
},
|
||||||
rid: id,
|
Policy::Block => SeedingPolicy::Block,
|
||||||
scope,
|
};
|
||||||
policy,
|
entries.push(SeedPolicy { rid: id, policy });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Ok(Box::new(entries.into_iter()))
|
Ok(Box::new(entries.into_iter()))
|
||||||
}
|
}
|
||||||
|
|
@ -415,9 +414,15 @@ mod test {
|
||||||
let mut db = Store::open(":memory:").unwrap();
|
let mut db = Store::open(":memory:").unwrap();
|
||||||
|
|
||||||
assert!(db.seed(&id, Scope::All).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!(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]
|
#[test]
|
||||||
|
|
@ -426,9 +431,10 @@ mod test {
|
||||||
let mut db = Store::open(":memory:").unwrap();
|
let mut db = Store::open(":memory:").unwrap();
|
||||||
|
|
||||||
assert!(db.seed(&id, Scope::All).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!(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]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ use std::path::{Path, PathBuf};
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use serde_json as json;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::crypto::ssh::agent::Agent;
|
use crate::crypto::ssh::agent::Agent;
|
||||||
|
|
@ -22,13 +23,16 @@ use crate::crypto::ssh::{keystore, Keystore, Passphrase};
|
||||||
use crate::crypto::{PublicKey, Signer};
|
use crate::crypto::{PublicKey, Signer};
|
||||||
use crate::explorer::Explorer;
|
use crate::explorer::Explorer;
|
||||||
use crate::node::policy::config::store::Read;
|
use crate::node::policy::config::store::Read;
|
||||||
use crate::node::{notifications, policy, Alias, AliasStore};
|
use crate::node::{
|
||||||
use crate::prelude::Did;
|
notifications, policy,
|
||||||
use crate::prelude::NodeId;
|
policy::{Policy, Scope, SeedingPolicy},
|
||||||
|
Alias, AliasStore,
|
||||||
|
};
|
||||||
|
use crate::prelude::{Did, NodeId};
|
||||||
use crate::storage::git::transport;
|
use crate::storage::git::transport;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::storage::{self, ReadRepository};
|
use crate::storage::ReadRepository;
|
||||||
use crate::{cli, cob, git, node, web};
|
use crate::{cli, cob, git, node, storage, web};
|
||||||
|
|
||||||
/// Environment variables used by radicle.
|
/// Environment variables used by radicle.
|
||||||
pub mod env {
|
pub mod env {
|
||||||
|
|
@ -229,12 +233,28 @@ impl Config {
|
||||||
|
|
||||||
/// Load a configuration from the given path.
|
/// Load a configuration from the given path.
|
||||||
pub fn load(path: &Path) -> Result<Self, ConfigError> {
|
pub fn load(path: &Path) -> Result<Self, ConfigError> {
|
||||||
match fs::File::open(path) {
|
let mut cfg: Self = match fs::File::open(path) {
|
||||||
Ok(cfg) => {
|
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.
|
/// Write configuration to disk.
|
||||||
|
|
@ -243,8 +263,8 @@ impl Config {
|
||||||
.create_new(true)
|
.create_new(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(path)?;
|
.open(path)?;
|
||||||
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
|
let formatter = json::ser::PrettyFormatter::with_indent(b" ");
|
||||||
let mut serializer = serde_json::Serializer::with_formatter(&file, formatter);
|
let mut serializer = json::Serializer::with_formatter(&file, formatter);
|
||||||
|
|
||||||
self.serialize(&mut serializer)?;
|
self.serialize(&mut serializer)?;
|
||||||
file.write_all(b"\n")?;
|
file.write_all(b"\n")?;
|
||||||
|
|
@ -315,7 +335,6 @@ impl Profile {
|
||||||
key: public_key,
|
key: public_key,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
transport::local::register(storage.clone());
|
transport::local::register(storage.clone());
|
||||||
|
|
||||||
Ok(Profile {
|
Ok(Profile {
|
||||||
|
|
@ -382,8 +401,7 @@ impl Profile {
|
||||||
pub fn policies(&self) -> Result<policy::config::Config<Read>, policy::store::Error> {
|
pub fn policies(&self) -> Result<policy::config::Config<Read>, policy::store::Error> {
|
||||||
let path = self.node().join(node::POLICIES_DB_FILE);
|
let path = self.node().join(node::POLICIES_DB_FILE);
|
||||||
let config = policy::config::Config::new(
|
let config = policy::config::Config::new(
|
||||||
self.config.node.policy,
|
self.config.node.seeding_policy,
|
||||||
self.config.node.scope,
|
|
||||||
policy::store::Store::reader(path)?,
|
policy::store::Store::reader(path)?,
|
||||||
);
|
);
|
||||||
Ok(config)
|
Ok(config)
|
||||||
|
|
@ -629,10 +647,9 @@ impl Home {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use super::Home;
|
|
||||||
|
|
||||||
// Checks that if we have:
|
// Checks that if we have:
|
||||||
// '/run/user/1000/.tmpqfK6ih/../.tmpqfK6ih/Radicle/Home'
|
// '/run/user/1000/.tmpqfK6ih/../.tmpqfK6ih/Radicle/Home'
|
||||||
//
|
//
|
||||||
|
|
@ -656,4 +673,55 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(home.path, path);
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue