node: Make node aliases required
This is a fairly substantial change, which adds a new configuration file to the user's `$RAD_HOME` that includes node configuration, including the alias, and also makes node aliases required. When running `rad auth`, the user is now prompted for an alias, which defaults to `$USER`. When running `rad self`, the alias is now shown. If the user runs radicle without a config file, the defaults are loaded, and `$USER` is used as the alias.
This commit is contained in:
parent
8ed55a1925
commit
eb701be033
|
|
@ -1,30 +1,30 @@
|
|||
```
|
||||
$ cd heartwood
|
||||
$ git branch -r
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
rad/master
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
```
|
||||
|
||||
```
|
||||
$ git ls-remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/heads/*'
|
||||
$ git ls-remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/heads/*'
|
||||
145e1e69bef3ad93d14946ea212249c2fa9b9828 refs/heads/alice/1
|
||||
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
|
||||
```
|
||||
|
||||
``` (stderr)
|
||||
$ git fetch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
$ git fetch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
* [new branch] alice/1 -> z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
|
||||
* [new branch] alice/1 -> alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
|
||||
```
|
||||
|
||||
```
|
||||
$ git branch -r
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
rad/master
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
```
|
||||
|
||||
```
|
||||
$ git rev-parse z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
|
||||
$ git rev-parse alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
|
||||
145e1e69bef3ad93d14946ea212249c2fa9b9828
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ Initializing a new identity with `rad-auth`.
|
|||
The example below is run with `RAD_PASSPHRASE` set.
|
||||
|
||||
```
|
||||
$ rad auth
|
||||
$ rad auth --alias "alice"
|
||||
|
||||
Initializing your radicle 👾 identity
|
||||
|
||||
|
|
@ -18,3 +18,9 @@ You can get the above information at all times using the `self` command:
|
|||
$ rad self --did
|
||||
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
```
|
||||
|
||||
You can also show your alias:
|
||||
```
|
||||
$ rad self --alias
|
||||
alice
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all
|
|||
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
||||
✓ Forking under z6Mkux1…nVhib7Z..
|
||||
✓ Creating checkout in ./heartwood..
|
||||
✓ Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ Repository successfully cloned under [..]/heartwood/
|
||||
```
|
||||
|
||||
|
|
@ -68,10 +68,10 @@ And fetch his refs:
|
|||
```
|
||||
$ git fetch --all
|
||||
Fetching rad
|
||||
Fetching z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
Fetching alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
Fetching bob
|
||||
$ git branch --remotes
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
bob/master
|
||||
rad/master
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope trusted
|
|||
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
||||
✓ Forking under z6Mkt67…v4N1tRk..
|
||||
✓ Creating checkout in ./heartwood..
|
||||
✓ Remote z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
|
||||
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
|
||||
✓ Repository successfully cloned under [..]/heartwood/
|
||||
```
|
||||
|
||||
|
|
@ -27,8 +27,8 @@ Let's check that the remote tracking branch was setup correctly:
|
|||
|
||||
```
|
||||
$ git branch --remotes
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
rad/master
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
```
|
||||
|
||||
The first branch is ours, and the second points to the repository delegate.
|
||||
|
|
@ -36,10 +36,10 @@ We can also take a look at the remotes:
|
|||
|
||||
```
|
||||
$ git remote -v
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
|
||||
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji (fetch)
|
||||
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push)
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
|
||||
```
|
||||
|
||||
Let's check the last commit!
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ The `rad inspect` command can be run without being authenticated with radicle:
|
|||
|
||||
``` (fail)
|
||||
$ rad self
|
||||
✗ Self failed: Could not load radicle profile
|
||||
✗ Self failed: Could not load radicle profile: no profile found at path [..]
|
||||
✗ Hint: To setup your radicle profile, run `rad auth`.
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkE
|
|||
```
|
||||
```
|
||||
$ rad patch --merged
|
||||
╭──────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ● ID Title Author Head + - Updated │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ✔ 0ec956c First change z6MknSL…StBU8Vi (you) 20aa5dd +0 -0 [ ... ] │
|
||||
│ ✔ 928d76e Second change z6MknSL…StBU8Vi (you) daf349f +0 -0 [ ... ] │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────╯
|
||||
╭────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ● ID Title Author Head + - Updated │
|
||||
├────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ✔ 0ec956c First change z6MknSL…StBU8Vi alice (you) 20aa5dd +0 -0 [ ... ] │
|
||||
│ ✔ 928d76e Second change z6MknSL…StBU8Vi alice (you) daf349f +0 -0 [ ... ] │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
We can verify that the remote tracking branches were also deleted:
|
||||
|
|
|
|||
|
|
@ -48,6 +48,6 @@ able to fetch the node alias from our db!
|
|||
|
||||
```
|
||||
$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
✓ Remote bob added
|
||||
✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
|
||||
✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added
|
||||
✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ device and node.
|
|||
|
||||
```
|
||||
$ rad self
|
||||
Alias alice
|
||||
DID did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
└╴Node ID (NID) z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
SSH not running
|
||||
├╴Key (hash) SHA256:UIedaL6Cxm6OUErh9GQUzzglSk7VpQlVTI1TAFB/HWA
|
||||
└╴Key (full) ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHahWSBEpuT1ESZbynOmBNkLBSnR32Ar4woZqSV2YNH1
|
||||
Home [..]/home/alice/.radicle
|
||||
├╴Config [..]/home/alice/.radicle/config.json
|
||||
├╴Storage [..]/home/alice/.radicle/storage
|
||||
├╴Keys [..]/home/alice/.radicle/keys
|
||||
└╴Node [..]/home/alice/.radicle/node
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ $ rad patch show 50e29a1
|
|||
│ ● opened by bob (z6Mkt67…v4N1tRk) [ ... ] │
|
||||
│ ↑ updated to 3530243d46a2e7a8e4eac7afcbb17cc7c56b3d29 (27857ec) [ ... ] │
|
||||
│ ↑ updated to 744c1f0a75b1c42833c9aa32f79cd40443925d66 (f567f69) [ ... ] │
|
||||
│ ✓ merged by (you) [ ... ] │
|
||||
│ ✓ merged by alice (you) [ ... ] │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ $ git pull --all --ff
|
|||
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
f2de534..f567f69 master -> rad/master
|
||||
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
f2de534..f567f69 master -> z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
f2de534..f567f69 master -> alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
```
|
||||
|
||||
Now our master branch is up to date with the maintainer's master:
|
||||
|
|
@ -32,5 +32,5 @@ Now our master branch is up to date with the maintainer's master:
|
|||
```
|
||||
$ git rev-parse master
|
||||
f567f695d25b4e8fb63b5f5ad2a584529826e908
|
||||
$ git diff master..z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
$ git diff master..alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
#![allow(clippy::or_fun_call)]
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use radicle::crypto::ssh;
|
||||
use radicle::crypto::ssh::Passphrase;
|
||||
use radicle::node::Alias;
|
||||
use radicle::profile::env::RAD_PASSPHRASE;
|
||||
use radicle::{profile, Profile};
|
||||
|
||||
|
|
@ -26,6 +29,7 @@ Usage
|
|||
|
||||
Options
|
||||
|
||||
--alias When initializing an identity, sets the node alias
|
||||
--stdin Read passphrase from stdin (default: false)
|
||||
--help Print help
|
||||
"#,
|
||||
|
|
@ -34,6 +38,7 @@ Options
|
|||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
pub stdin: bool,
|
||||
pub alias: Option<Alias>,
|
||||
}
|
||||
|
||||
impl Args for Options {
|
||||
|
|
@ -41,10 +46,17 @@ impl Args for Options {
|
|||
use lexopt::prelude::*;
|
||||
|
||||
let mut stdin = false;
|
||||
let mut alias = None;
|
||||
let mut parser = lexopt::Parser::from_args(args);
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
Long("alias") => {
|
||||
let val = parser.value()?;
|
||||
let val = term::args::alias(&val)?;
|
||||
|
||||
alias = Some(val);
|
||||
}
|
||||
Long("stdin") => {
|
||||
stdin = true;
|
||||
}
|
||||
|
|
@ -55,7 +67,7 @@ impl Args for Options {
|
|||
}
|
||||
}
|
||||
|
||||
Ok((Options { stdin }, vec![]))
|
||||
Ok((Options { alias, stdin }, vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +93,16 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
|||
anyhow::bail!("Error retrieving git version; please check your installation");
|
||||
}
|
||||
|
||||
let alias: Alias = if let Some(alias) = options.alias {
|
||||
alias
|
||||
} else {
|
||||
let user = env::var("USER").ok().and_then(|u| Alias::from_str(&u).ok());
|
||||
term::input(
|
||||
"Enter your alias:",
|
||||
user,
|
||||
Some("This is your node alias. You can always change it later"),
|
||||
)?
|
||||
};
|
||||
let home = profile::home()?;
|
||||
let passphrase = if options.stdin {
|
||||
term::passphrase_stdin()
|
||||
|
|
@ -88,7 +110,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
|||
term::passphrase_confirm("Enter a passphrase:", RAD_PASSPHRASE)
|
||||
}?;
|
||||
let spinner = term::spinner("Creating your Ed25519 keypair...");
|
||||
let profile = Profile::init(home, passphrase.clone())?;
|
||||
let profile = Profile::init(home, alias, passphrase.clone())?;
|
||||
spinner.finish();
|
||||
|
||||
match ssh::agent::Agent::connect() {
|
||||
|
|
|
|||
|
|
@ -160,14 +160,16 @@ pub fn setup_remote(
|
|||
remote_name: Option<git::RefString>,
|
||||
aliases: &impl AliasStore,
|
||||
) -> anyhow::Result<()> {
|
||||
let remote_name = if let Some(alias) = remote_name {
|
||||
alias
|
||||
let remote_name = if let Some(name) = remote_name {
|
||||
name
|
||||
} else {
|
||||
let alias = aliases
|
||||
.alias(remote_id)
|
||||
.unwrap_or_else(|| remote_id.to_string());
|
||||
git::RefString::try_from(alias.clone())
|
||||
.map_err(|_| anyhow!("invalid remote name: '{alias}'"))?
|
||||
let name = if let Some(alias) = aliases.alias(remote_id) {
|
||||
format!("{alias}@{remote_id}")
|
||||
} else {
|
||||
remote_id.to_human()
|
||||
};
|
||||
git::RefString::try_from(name.as_str())
|
||||
.map_err(|_| anyhow!("invalid remote name: '{name}'"))?
|
||||
};
|
||||
let (remote, branch) = setup.run(remote_name, *remote_id)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -198,14 +198,14 @@ pub fn init(options: Options, profile: &profile::Profile) -> anyhow::Result<()>
|
|||
|
||||
let name = options.name.unwrap_or_else(|| {
|
||||
let default = path.file_name().map(|f| f.to_string_lossy().to_string());
|
||||
term::input("Name", default).unwrap()
|
||||
term::input("Name", default, None).unwrap()
|
||||
});
|
||||
let description = options
|
||||
.description
|
||||
.unwrap_or_else(|| term::input("Description", None).unwrap());
|
||||
.unwrap_or_else(|| term::input("Description", None, None).unwrap());
|
||||
let branch = options.branch.unwrap_or_else(|| {
|
||||
if interactive.yes() {
|
||||
term::input("Default branch", Some(head)).unwrap()
|
||||
term::input("Default branch", Some(head), None).unwrap()
|
||||
} else {
|
||||
head
|
||||
}
|
||||
|
|
|
|||
|
|
@ -462,6 +462,7 @@ fn list(
|
|||
]);
|
||||
}
|
||||
table.print();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ fn print_nodes(store: &tracking::store::Config) -> anyhow::Result<()> {
|
|||
term::format::highlight(Did::from(id).to_string()),
|
||||
match alias {
|
||||
None => term::format::secondary(String::from("n/a")),
|
||||
Some(alias) => term::format::secondary(alias),
|
||||
Some(alias) => term::format::secondary(alias.to_string()),
|
||||
},
|
||||
term::format::secondary(policy.to_string()),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ pub fn run(
|
|||
/// Patch row.
|
||||
pub fn row(
|
||||
profile: &Profile,
|
||||
alias: Option<String>,
|
||||
alias: Option<Alias>,
|
||||
id: &PatchId,
|
||||
patch: &Patch,
|
||||
repository: &Repository,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Usage
|
|||
|
||||
Options
|
||||
|
||||
--alias Show your Node alias
|
||||
--nid Show your Node ID (NID)
|
||||
--did Show your DID
|
||||
--home Show your Radicle home
|
||||
|
|
@ -29,6 +30,7 @@ Options
|
|||
|
||||
#[derive(Debug)]
|
||||
enum Show {
|
||||
Alias,
|
||||
NodeId,
|
||||
Did,
|
||||
Home,
|
||||
|
|
@ -51,6 +53,9 @@ impl Args for Options {
|
|||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
Long("alias") if show.is_none() => {
|
||||
show = Some(Show::Alias);
|
||||
}
|
||||
Long("nid") if show.is_none() => {
|
||||
show = Some(Show::NodeId);
|
||||
}
|
||||
|
|
@ -86,6 +91,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
let profile = ctx.profile()?;
|
||||
|
||||
match options.show {
|
||||
Show::Alias => {
|
||||
term::print(profile.config.alias());
|
||||
}
|
||||
Show::NodeId => {
|
||||
term::print(profile.id());
|
||||
}
|
||||
|
|
@ -110,6 +118,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
fn all(profile: &Profile) -> anyhow::Result<()> {
|
||||
let mut table = term::Table::default();
|
||||
|
||||
table.push([
|
||||
term::format::style("Alias").to_string(),
|
||||
term::format::primary(profile.config.alias()).to_string(),
|
||||
]);
|
||||
|
||||
let did = profile.did();
|
||||
table.push([
|
||||
term::format::style("DID").to_string(),
|
||||
|
|
@ -153,6 +166,12 @@ fn all(profile: &Profile) -> anyhow::Result<()> {
|
|||
term::format::tertiary(home.display()).to_string(),
|
||||
]);
|
||||
|
||||
let config_path = profile.home.config();
|
||||
table.push([
|
||||
term::format::style("├╴Config").to_string(),
|
||||
term::format::tertiary(config_path.display()).to_string(),
|
||||
]);
|
||||
|
||||
let storage_path = profile.home.storage();
|
||||
table.push([
|
||||
term::format::style("├╴Storage").to_string(),
|
||||
|
|
|
|||
|
|
@ -79,10 +79,7 @@ impl Args for Options {
|
|||
}
|
||||
(Long("alias"), Some(Operation::TrackNode { alias, .. })) => {
|
||||
let name = parser.value()?;
|
||||
let name = name
|
||||
.to_str()
|
||||
.to_owned()
|
||||
.ok_or_else(|| anyhow!("alias specified is not UTF-8"))?;
|
||||
let name = term::args::alias(&name)?;
|
||||
|
||||
*alias = Some(name.to_owned());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,14 +113,13 @@ where
|
|||
|
||||
/// Get the default profile. Fails if there is no profile.
|
||||
pub fn profile() -> Result<Profile, anyhow::Error> {
|
||||
let error = args::Error::WithHint {
|
||||
err: anyhow::anyhow!("Could not load radicle profile"),
|
||||
hint: "To setup your radicle profile, run `rad auth`.",
|
||||
};
|
||||
|
||||
match Profile::load() {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(_) => Err(error.into()),
|
||||
Err(e) => Err(args::Error::WithHint {
|
||||
err: anyhow::anyhow!("Could not load radicle profile: {e}"),
|
||||
hint: "To setup your radicle profile, run `rad auth`.",
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use anyhow::anyhow;
|
|||
use radicle::cob::{self, issue, patch};
|
||||
use radicle::crypto;
|
||||
use radicle::git::RefString;
|
||||
use radicle::node::Address;
|
||||
use radicle::node::{Address, Alias};
|
||||
use radicle::prelude::{Did, Id, NodeId};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
@ -136,6 +136,15 @@ pub fn string(val: &OsString) -> String {
|
|||
val.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn alias(val: &OsString) -> anyhow::Result<Alias> {
|
||||
let val = val.as_os_str();
|
||||
let val = val
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow!("alias must be valid UTF-8"))?;
|
||||
|
||||
Alias::from_str(val).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn issue(val: &OsString) -> anyhow::Result<issue::IssueId> {
|
||||
let val = val.to_string_lossy();
|
||||
issue::IssueId::from_str(&val).map_err(|_| anyhow!("invalid Issue ID '{}'", val))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ pub use radicle_term::format::*;
|
|||
pub use radicle_term::{style, Paint};
|
||||
|
||||
use radicle::cob::{ObjectId, Timestamp};
|
||||
use radicle::node::{AliasStore, NodeId};
|
||||
use radicle::node::{Alias, AliasStore, NodeId};
|
||||
use radicle::prelude::Did;
|
||||
use radicle::profile::Profile;
|
||||
use radicle_term::element::Line;
|
||||
|
|
@ -143,15 +143,15 @@ impl<'a> fmt::Display for Identity<'a> {
|
|||
pub enum Author<'a> {
|
||||
Author {
|
||||
nid: &'a NodeId,
|
||||
alias: Option<String>,
|
||||
alias: Option<Alias>,
|
||||
},
|
||||
Me {
|
||||
alias: Option<String>,
|
||||
alias: Option<Alias>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Author<'a> {
|
||||
pub fn new(nid: &'a NodeId, alias: Option<String>, me: &Profile) -> Author<'a> {
|
||||
pub fn new(nid: &'a NodeId, alias: Option<Alias>, me: &Profile) -> Author<'a> {
|
||||
if nid == me.id() {
|
||||
Self::Me { alias }
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::str::FromStr;
|
|||
use std::{env, fs, thread, time};
|
||||
|
||||
use radicle::git;
|
||||
use radicle::node::Alias;
|
||||
use radicle::node::Handle as _;
|
||||
use radicle::prelude::Id;
|
||||
use radicle::profile::Home;
|
||||
|
|
@ -48,6 +49,7 @@ fn test<'a>(
|
|||
.env("EDITOR", "true")
|
||||
.env("TZ", "UTC")
|
||||
.env("LANG", "C")
|
||||
.env("USER", "alice")
|
||||
.env(radicle_cob::git::RAD_COMMIT_TIME, "1671125284")
|
||||
.envs(git::env::GIT_DEFAULT_CONFIG)
|
||||
.envs(envs)
|
||||
|
|
@ -234,12 +236,12 @@ fn rad_node() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
|
||||
let alice = alice.spawn(Config::default());
|
||||
let _bob = bob.spawn(Config::default());
|
||||
let alice = alice.spawn();
|
||||
let _bob = bob.spawn();
|
||||
|
||||
fixtures::repository(working.path().join("alice"));
|
||||
|
||||
|
|
@ -398,9 +400,9 @@ fn rad_rm() {
|
|||
#[test]
|
||||
fn rad_track() {
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let alice = alice.spawn(Config::default());
|
||||
let alice = alice.spawn();
|
||||
|
||||
test(
|
||||
"examples/rad-track.md",
|
||||
|
|
@ -416,15 +418,15 @@ fn rad_clone() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let mut alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let mut alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
// Setup a test project.
|
||||
let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
// Prevent Alice from fetching Bob's fork, as we're not testing that and it may cause errors.
|
||||
alice.handle.track_repo(acme, Scope::Trusted).unwrap();
|
||||
|
||||
|
|
@ -438,17 +440,17 @@ fn rad_clone_all() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let mut alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let eve = environment.node("eve");
|
||||
let mut alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let eve = environment.node(Config::new(Alias::new("eve")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
// Setup a test project.
|
||||
let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let mut eve = eve.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
|
||||
alice.handle.track_repo(acme, Scope::All).unwrap();
|
||||
bob.connect(&alice).converge([&alice]);
|
||||
|
|
@ -477,7 +479,7 @@ fn rad_clone_all() {
|
|||
#[test]
|
||||
fn rad_self() {
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
test("examples/rad-self.md", working, Some(&alice.home), []).unwrap();
|
||||
|
|
@ -488,10 +490,10 @@ fn rad_clone_unknown() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
let alice = alice.spawn(Config::default());
|
||||
let alice = alice.spawn();
|
||||
|
||||
test(
|
||||
"examples/rad-clone-unknown.md",
|
||||
|
|
@ -507,12 +509,12 @@ fn rad_init_sync_and_clone() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
let alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
bob.connect(&alice);
|
||||
|
||||
|
|
@ -548,11 +550,11 @@ fn rad_init_sync_and_clone() {
|
|||
fn rad_fetch() {
|
||||
let mut environment = Environment::new();
|
||||
let working = environment.tmp().join("working");
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let bob = bob.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
fixtures::repository(working.join("alice"));
|
||||
|
|
@ -582,11 +584,11 @@ fn rad_fetch() {
|
|||
fn rad_fork() {
|
||||
let mut environment = Environment::new();
|
||||
let working = environment.tmp().join("working");
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let bob = bob.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
fixtures::repository(working.join("alice"));
|
||||
|
|
@ -625,10 +627,10 @@ fn test_clone_without_seeds() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let mut alice = environment.node("alice");
|
||||
let mut alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let working = environment.tmp().join("working");
|
||||
let rid = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let seeds = alice.handle.seeds(rid).unwrap();
|
||||
|
||||
assert!(!seeds.has_connections());
|
||||
|
|
@ -648,13 +650,13 @@ fn test_cob_replication() {
|
|||
|
||||
let mut environment = Environment::new();
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let mut alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let mut alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
|
||||
let rid = alice.project("heartwood", "");
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let events = alice.handle.events();
|
||||
|
||||
alice.handle.track_node(bob.id, None).unwrap();
|
||||
|
|
@ -708,13 +710,13 @@ fn test_cob_replication() {
|
|||
fn test_cob_deletion() {
|
||||
let mut environment = Environment::new();
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let mut alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let mut alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
|
||||
let rid = alice.project("heartwood", "");
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
alice.handle.track_repo(rid, Scope::All).unwrap();
|
||||
bob.handle.track_repo(rid, Scope::All).unwrap();
|
||||
|
|
@ -762,9 +764,9 @@ fn rad_sync() {
|
|||
|
||||
let mut environment = Environment::new();
|
||||
let working = environment.tmp().join("working");
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let eve = environment.node("eve");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let eve = environment.node(Config::new(Alias::new("eve")));
|
||||
let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
|
||||
fixtures::repository(working.join("acme"));
|
||||
|
|
@ -777,9 +779,9 @@ fn rad_sync() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let mut eve = eve.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
|
||||
bob.handle.track_repo(acme, Scope::All).unwrap();
|
||||
eve.handle.track_repo(acme, Scope::All).unwrap();
|
||||
|
|
@ -806,19 +808,19 @@ fn rad_sync() {
|
|||
//
|
||||
fn test_replication_via_seed() {
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let seed = environment.node("seed");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let seed = environment.node(Config {
|
||||
policy: Policy::Track,
|
||||
scope: Scope::All,
|
||||
..Config::new(Alias::new("seed"))
|
||||
});
|
||||
let working = environment.tmp().join("working");
|
||||
let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let seed = seed.spawn(Config {
|
||||
policy: Policy::Track,
|
||||
scope: Scope::All,
|
||||
..Config::default()
|
||||
});
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let seed = seed.spawn();
|
||||
|
||||
alice.connect(&seed);
|
||||
bob.connect(&seed);
|
||||
|
|
@ -891,8 +893,8 @@ fn test_replication_via_seed() {
|
|||
#[test]
|
||||
fn rad_remote() {
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let working = environment.tmp().join("working");
|
||||
let home = alice.home.clone();
|
||||
let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
|
|
@ -907,11 +909,11 @@ fn rad_remote() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let mut alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
alice
|
||||
.handle
|
||||
.track_node(bob.id, Some("bob".to_owned()))
|
||||
.track_node(bob.id, Some(Alias::new("bob")))
|
||||
.unwrap();
|
||||
|
||||
bob.connect(&alice);
|
||||
|
|
@ -935,7 +937,7 @@ fn rad_merge_via_push() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
fixtures::repository(working.join("alice"));
|
||||
|
|
@ -948,7 +950,7 @@ fn rad_merge_via_push() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let alice = alice.spawn(Config::default());
|
||||
let alice = alice.spawn();
|
||||
|
||||
test(
|
||||
"examples/rad-merge-via-push.md",
|
||||
|
|
@ -964,8 +966,8 @@ fn git_push_and_pull() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
fixtures::repository(working.join("alice"));
|
||||
|
|
@ -978,8 +980,8 @@ fn git_push_and_pull() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config::default());
|
||||
let alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
bob.connect(&alice).converge([&alice]);
|
||||
|
||||
|
|
@ -1016,8 +1018,8 @@ fn git_push_and_pull() {
|
|||
#[test]
|
||||
fn rad_workflow() {
|
||||
let mut environment = Environment::new();
|
||||
let alice = environment.node("alice");
|
||||
let bob = environment.node("bob");
|
||||
let alice = environment.node(Config::new(Alias::new("alice")));
|
||||
let bob = environment.node(Config::new(Alias::new("bob")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
fixtures::repository(working.join("alice"));
|
||||
|
|
@ -1030,11 +1032,8 @@ fn rad_workflow() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let alice = alice.spawn(Config::default());
|
||||
let mut bob = bob.spawn(Config {
|
||||
alias: Some("bob".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
let alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
bob.connect(&alice).converge([&alice]);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use radicle::cob::thread;
|
|||
use radicle::cob::thread::CommentId;
|
||||
use radicle::cob::{ActorId, Author, Reaction, Timestamp};
|
||||
use radicle::git::RefString;
|
||||
use radicle::node::AliasStore;
|
||||
use radicle::node::{Alias, AliasStore};
|
||||
use radicle::prelude::NodeId;
|
||||
use radicle::storage::{git, refs, ReadRepository};
|
||||
use radicle_surf::blob::Blob;
|
||||
|
|
@ -146,7 +146,7 @@ pub(crate) fn patch(
|
|||
}
|
||||
|
||||
/// Returns JSON for an `author` and fills in `alias` when present.
|
||||
fn author(author: &Author, alias: Option<String>) -> Value {
|
||||
fn author(author: &Author, alias: Option<Alias>) -> Value {
|
||||
match alias {
|
||||
Some(alias) => json!({
|
||||
"id": author.id,
|
||||
|
|
@ -157,7 +157,7 @@ fn author(author: &Author, alias: Option<String>) -> Value {
|
|||
}
|
||||
|
||||
/// Returns JSON for a patch `Merge` and fills in `alias` when present.
|
||||
fn merge(merge: &Merge, nid: &NodeId, alias: Option<String>) -> Value {
|
||||
fn merge(merge: &Merge, nid: &NodeId, alias: Option<Alias>) -> Value {
|
||||
match alias {
|
||||
Some(alias) => json!({
|
||||
"author": {
|
||||
|
|
@ -180,7 +180,7 @@ fn merge(merge: &Merge, nid: &NodeId, alias: Option<String>) -> Value {
|
|||
}
|
||||
|
||||
/// Returns JSON for a patch `Review` and fills in `alias` when present.
|
||||
fn review(nid: &NodeId, alias: Option<String>, review: &Review) -> Value {
|
||||
fn review(nid: &NodeId, alias: Option<Alias>, review: &Review) -> Value {
|
||||
match alias {
|
||||
Some(alias) => json!({
|
||||
"author": {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ use radicle::crypto::ssh::keystore::MemorySigner;
|
|||
use radicle::crypto::ssh::Keystore;
|
||||
use radicle::crypto::{KeyPair, Seed, Signer};
|
||||
use radicle::git::{raw as git2, RefString};
|
||||
use radicle::node;
|
||||
use radicle::node::address as AddressStore;
|
||||
use radicle::node::routing as RoutingStore;
|
||||
use radicle::node::tracking::store as TrackingStore;
|
||||
use radicle::profile;
|
||||
use radicle::profile::Home;
|
||||
use radicle::storage::ReadStorage;
|
||||
use radicle::Storage;
|
||||
|
|
@ -63,6 +65,9 @@ pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
|||
storage,
|
||||
keystore,
|
||||
public_key: keypair.pk.into(),
|
||||
config: profile::Config {
|
||||
node: node::Config::new(node::Alias::new("seed")),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use serde_json as json;
|
|||
|
||||
use crate::identity::Id;
|
||||
use crate::node::NodeId;
|
||||
use crate::node::{Command, CommandName, CommandResult};
|
||||
use crate::node::{Alias, Command, CommandName, CommandResult};
|
||||
use crate::runtime;
|
||||
use crate::runtime::thread;
|
||||
|
||||
|
|
@ -150,6 +150,12 @@ where
|
|||
[node, alias] => (node.as_str(), Some(alias.to_owned())),
|
||||
_ => return Err(CommandError::InvalidCommandArgs(cmd.args)),
|
||||
};
|
||||
let alias = alias
|
||||
.map(|a| {
|
||||
Alias::from_str(&a)
|
||||
.map_err(|e| CommandError::InvalidCommandArg(node.to_owned(), Box::new(e)))
|
||||
})
|
||||
.transpose()?;
|
||||
let nid = node
|
||||
.parse()
|
||||
.map_err(|e| CommandError::InvalidCommandArg(node.to_owned(), Box::new(e)))?;
|
||||
|
|
@ -358,12 +364,8 @@ mod tests {
|
|||
assert!(handle.untrack_repo(proj).unwrap());
|
||||
assert!(!handle.untrack_repo(proj).unwrap());
|
||||
|
||||
assert!(handle
|
||||
.track_node(peer, Some(String::from("alice")))
|
||||
.unwrap());
|
||||
assert!(!handle
|
||||
.track_node(peer, Some(String::from("alice")))
|
||||
.unwrap());
|
||||
assert!(handle.track_node(peer, Some(Alias::new("alice"))).unwrap());
|
||||
assert!(!handle.track_node(peer, Some(Alias::new("alice"))).unwrap());
|
||||
assert!(handle.untrack_node(peer).unwrap());
|
||||
assert!(!handle.untrack_node(peer).unwrap());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ use crossbeam_channel as chan;
|
|||
use cyphernet::addr::PeerAddr;
|
||||
use localtime::LocalDuration;
|
||||
|
||||
use radicle::node;
|
||||
use radicle::prelude::Signer;
|
||||
use radicle::profile;
|
||||
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
|
||||
use radicle_node::prelude::{Address, NodeId};
|
||||
use radicle_node::service::tracking::{Policy, Scope};
|
||||
use radicle_node::Runtime;
|
||||
use radicle_node::{logger, service, signals};
|
||||
use radicle_term as term;
|
||||
|
|
@ -21,7 +21,6 @@ Usage
|
|||
|
||||
Options
|
||||
|
||||
--alias <alias> Identify yourself with an alias on the network
|
||||
--connect <peer> Connect to the given peer address on start
|
||||
--external-address <address> Publicly accessible address (default 0.0.0.0:8776)
|
||||
--git-daemon <address> Address to bind git-daemon to (default 0.0.0.0:9418)
|
||||
|
|
@ -34,48 +33,29 @@ Options
|
|||
|
||||
#[derive(Debug)]
|
||||
struct Options {
|
||||
alias: Option<String>,
|
||||
connect: Vec<(NodeId, Address)>,
|
||||
external_addresses: Vec<Address>,
|
||||
daemon: Option<net::SocketAddr>,
|
||||
limits: service::config::Limits,
|
||||
listen: Vec<net::SocketAddr>,
|
||||
force: bool,
|
||||
tracking_policy: Policy,
|
||||
tracking_scope: Scope,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn from_env() -> Result<Self, anyhow::Error> {
|
||||
fn from_env(config: &mut node::Config) -> Result<Self, anyhow::Error> {
|
||||
use lexopt::prelude::*;
|
||||
|
||||
let mut parser = lexopt::Parser::from_env();
|
||||
let mut alias = None;
|
||||
let mut connect = Vec::new();
|
||||
let mut external_addresses = Vec::new();
|
||||
let mut limits = service::config::Limits::default();
|
||||
let mut listen = Vec::new();
|
||||
let mut daemon = None;
|
||||
let mut tracking_policy = Policy::default();
|
||||
let mut tracking_scope = Scope::default();
|
||||
let mut force = false;
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
Long("alias") => {
|
||||
let name: String = parser.value()?.parse()?;
|
||||
if name.len() > 32 {
|
||||
anyhow::bail!("alias '{}' is longer than 32 characters", name);
|
||||
}
|
||||
alias = Some(name);
|
||||
}
|
||||
Long("connect") => {
|
||||
let peer: PeerAddr<NodeId, Address> = parser.value()?.parse()?;
|
||||
connect.push((peer.id, peer.addr.clone()));
|
||||
config.connect.push(peer.into());
|
||||
}
|
||||
Long("external-address") => {
|
||||
let addr = parser.value()?.parse()?;
|
||||
external_addresses.push(addr);
|
||||
config.external_addresses.push(addr);
|
||||
}
|
||||
Long("force") => {
|
||||
force = true;
|
||||
|
|
@ -89,24 +69,24 @@ impl Options {
|
|||
.value()?
|
||||
.parse()
|
||||
.map_err(|s| anyhow!("unknown tracking policy {:?}", s))?;
|
||||
tracking_policy = policy;
|
||||
config.policy = policy;
|
||||
}
|
||||
Long("tracking-scope") => {
|
||||
let scope = parser
|
||||
.value()?
|
||||
.parse()
|
||||
.map_err(|s| anyhow!("unknown tracking scope {:?}", s))?;
|
||||
tracking_scope = scope;
|
||||
config.scope = scope;
|
||||
}
|
||||
Long("limit-routing-max-age") => {
|
||||
let secs: u64 = parser.value()?.parse()?;
|
||||
limits.routing_max_age = LocalDuration::from_secs(secs);
|
||||
config.limits.routing_max_age = LocalDuration::from_secs(secs);
|
||||
}
|
||||
Long("limit-routing-max-size") => {
|
||||
limits.routing_max_size = parser.value()?.parse()?;
|
||||
config.limits.routing_max_size = parser.value()?.parse()?;
|
||||
}
|
||||
Long("limit-fetch-concurrency") => {
|
||||
limits.fetch_concurrency = parser.value()?.parse()?;
|
||||
config.limits.fetch_concurrency = parser.value()?.parse()?;
|
||||
}
|
||||
Long("listen") => {
|
||||
let addr = parser.value()?.parse()?;
|
||||
|
|
@ -120,7 +100,7 @@ impl Options {
|
|||
}
|
||||
}
|
||||
|
||||
if external_addresses.len() > service::ADDRESS_LIMIT {
|
||||
if config.external_addresses.len() > service::ADDRESS_LIMIT {
|
||||
anyhow::bail!(
|
||||
"external address limit ({}) exceeded",
|
||||
service::ADDRESS_LIMIT,
|
||||
|
|
@ -128,15 +108,9 @@ impl Options {
|
|||
}
|
||||
|
||||
Ok(Self {
|
||||
alias,
|
||||
connect,
|
||||
daemon,
|
||||
external_addresses,
|
||||
force,
|
||||
limits,
|
||||
listen,
|
||||
tracking_policy,
|
||||
tracking_scope,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -144,13 +118,14 @@ impl Options {
|
|||
fn execute() -> anyhow::Result<()> {
|
||||
logger::init(log::Level::Debug)?;
|
||||
|
||||
let options = Options::from_env()?;
|
||||
|
||||
log::info!(target: "node", "Starting node..");
|
||||
log::info!(target: "node", "Version {} ({})", env!("CARGO_PKG_VERSION"), env!("GIT_HEAD"));
|
||||
log::info!(target: "node", "Unlocking node keystore..");
|
||||
|
||||
let home = profile::home()?;
|
||||
let mut config = profile::Config::load(&home.config())?.node;
|
||||
|
||||
log::info!(target: "node", "Unlocking node keystore..");
|
||||
|
||||
let passphrase = term::io::passphrase(profile::env::RAD_PASSPHRASE)
|
||||
.context(format!("`{}` must be set", profile::env::RAD_PASSPHRASE))?;
|
||||
let keystore = Keystore::new(&home.keys());
|
||||
|
|
@ -158,15 +133,7 @@ fn execute() -> anyhow::Result<()> {
|
|||
|
||||
log::info!(target: "node", "Node ID is {}", signer.public_key());
|
||||
|
||||
let config = service::Config {
|
||||
alias: options.alias,
|
||||
connect: options.connect.into_iter().collect(),
|
||||
external_addresses: options.external_addresses,
|
||||
limits: options.limits,
|
||||
policy: options.tracking_policy,
|
||||
scope: options.tracking_scope,
|
||||
..service::Config::default()
|
||||
};
|
||||
let options = Options::from_env(&mut config)?;
|
||||
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
|
||||
let daemon = options.daemon.unwrap_or_else(|| {
|
||||
net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), radicle::git::PROTOCOL_PORT)
|
||||
|
|
|
|||
|
|
@ -154,7 +154,10 @@ impl Runtime {
|
|||
.ok()
|
||||
.and_then(|ann| NodeAnnouncement::decode(&mut ann.as_slice()).ok())
|
||||
.and_then(|ann| {
|
||||
if config.matches(&ann) {
|
||||
if config.features() == ann.features
|
||||
&& config.alias == ann.alias
|
||||
&& config.external_addresses == ann.addresses.as_ref()
|
||||
{
|
||||
Some(ann)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -168,8 +171,7 @@ impl Runtime {
|
|||
);
|
||||
ann
|
||||
} else {
|
||||
config
|
||||
.node(clock.as_secs())
|
||||
service::gossip::node(&config, clock.as_secs())
|
||||
.solve(Default::default())
|
||||
.expect("Runtime::init: unable to solve proof-of-work puzzle")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use reactor::poller::popol::PopolWaker;
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::identity::Id;
|
||||
use crate::node::{Command, FetchResult};
|
||||
use crate::node::{Alias, Command, FetchResult};
|
||||
use crate::profile::Home;
|
||||
use crate::runtime::Emitter;
|
||||
use crate::service;
|
||||
|
|
@ -161,7 +161,7 @@ impl radicle::node::Handle for Handle {
|
|||
receiver.recv().map_err(Error::from)
|
||||
}
|
||||
|
||||
fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Error> {
|
||||
fn track_node(&mut self, id: NodeId, alias: Option<Alias>) -> Result<bool, Error> {
|
||||
let (sender, receiver) = chan::bounded(1);
|
||||
self.command(service::Command::TrackNode(id, alias, sender))?;
|
||||
receiver.recv().map_err(Error::from)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
#![allow(clippy::collapsible_match)]
|
||||
#![allow(clippy::collapsible_if)]
|
||||
pub mod config;
|
||||
pub mod filter;
|
||||
pub mod io;
|
||||
pub mod message;
|
||||
|
|
@ -28,7 +27,7 @@ use crate::identity::IdentityError;
|
|||
use crate::identity::{Doc, Id};
|
||||
use crate::node::routing;
|
||||
use crate::node::routing::InsertResult;
|
||||
use crate::node::{Address, Features, FetchResult, Seed, Seeds};
|
||||
use crate::node::{Address, Alias, Features, FetchResult, Seed, Seeds};
|
||||
use crate::prelude::*;
|
||||
use crate::runtime::Emitter;
|
||||
use crate::service::message::{Announcement, AnnouncementMessage, Ping};
|
||||
|
|
@ -41,8 +40,7 @@ use crate::worker::FetchError;
|
|||
use crate::Link;
|
||||
|
||||
pub use crate::node::events::{Event, Events};
|
||||
pub use crate::node::NodeId;
|
||||
pub use crate::service::config::{Config, Network};
|
||||
pub use crate::node::{config::Network, Config, NodeId};
|
||||
pub use crate::service::message::{Message, ZeroBytes};
|
||||
pub use crate::service::session::Session;
|
||||
|
||||
|
|
@ -137,7 +135,7 @@ pub enum Command {
|
|||
/// Untrack the given repository.
|
||||
UntrackRepo(Id, chan::Sender<bool>),
|
||||
/// Track the given node.
|
||||
TrackNode(NodeId, Option<String>, chan::Sender<bool>),
|
||||
TrackNode(NodeId, Option<Alias>, chan::Sender<bool>),
|
||||
/// Untrack the given node.
|
||||
UntrackNode(NodeId, chan::Sender<bool>),
|
||||
/// Query the internal service state.
|
||||
|
|
@ -382,7 +380,7 @@ where
|
|||
|
||||
// Connect to configured peers.
|
||||
let addrs = self.config.connect.clone();
|
||||
for (id, addr) in addrs {
|
||||
for (id, addr) in addrs.into_iter().map(|ca| ca.into()) {
|
||||
self.connect(id, addr);
|
||||
}
|
||||
// Ensure that our inventory is recorded in our routing table, and we are tracking
|
||||
|
|
@ -405,7 +403,7 @@ where
|
|||
.insert(
|
||||
&self.node_id(),
|
||||
self.node.features,
|
||||
self.node.alias().unwrap_or_default(),
|
||||
self.node.alias.clone(),
|
||||
self.node.work(),
|
||||
self.node.timestamp,
|
||||
self.node
|
||||
|
|
@ -998,14 +996,6 @@ where
|
|||
return Ok(false);
|
||||
}
|
||||
|
||||
let alias = match ann.alias() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!(target: "service", "Dropping node announcement from {announcer}: invalid alias: {e}");
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
// If this node isn't a seed, we're not interested in adding it
|
||||
// to our address book, but other nodes may be, so we relay the message anyway.
|
||||
if !features.has(Features::SEED) {
|
||||
|
|
@ -1015,7 +1005,7 @@ where
|
|||
match self.addresses.insert(
|
||||
announcer,
|
||||
*features,
|
||||
alias,
|
||||
ann.alias.clone(),
|
||||
ann.work(),
|
||||
timestamp,
|
||||
addresses
|
||||
|
|
@ -1781,7 +1771,7 @@ impl DerefMut for Sessions {
|
|||
}
|
||||
}
|
||||
|
||||
mod gossip {
|
||||
pub mod gossip {
|
||||
use super::*;
|
||||
use crate::service::filter::Filter;
|
||||
|
||||
|
|
@ -1842,6 +1832,24 @@ mod gossip {
|
|||
]
|
||||
}
|
||||
|
||||
pub fn node(config: &Config, timestamp: Timestamp) -> NodeAnnouncement {
|
||||
let features = config.features();
|
||||
let alias = config.alias.clone();
|
||||
let addresses: BoundedVec<_, ADDRESS_LIMIT> = config
|
||||
.external_addresses
|
||||
.clone()
|
||||
.try_into()
|
||||
.expect("external addresses are within the limit");
|
||||
|
||||
NodeAnnouncement {
|
||||
features,
|
||||
timestamp,
|
||||
alias,
|
||||
addresses,
|
||||
nonce: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inventory(timestamp: Timestamp, inventory: Vec<Id>) -> InventoryAnnouncement {
|
||||
type Inventory = BoundedVec<Id, INVENTORY_LIMIT>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
use localtime::LocalDuration;
|
||||
|
||||
use radicle::node;
|
||||
use radicle::node::Address;
|
||||
|
||||
use crate::bounded::BoundedVec;
|
||||
use crate::service::message::{NodeAnnouncement, ADDRESS_LIMIT};
|
||||
use crate::service::tracking::{Policy, Scope};
|
||||
use crate::service::NodeId;
|
||||
|
||||
/// Peer-to-peer network.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Network {
|
||||
#[default]
|
||||
Main,
|
||||
Test,
|
||||
}
|
||||
|
||||
/// Configuration parameters defining attributes of minima and maxima.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Limits {
|
||||
/// Number of routing table entries before we start pruning.
|
||||
pub routing_max_size: usize,
|
||||
/// How long to keep a routing table entry before being pruned.
|
||||
pub routing_max_age: LocalDuration,
|
||||
/// Maximum number of concurrent fetches per per connection.
|
||||
pub fetch_concurrency: usize,
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
routing_max_size: 1000,
|
||||
routing_max_age: LocalDuration::from_mins(7 * 24 * 60),
|
||||
fetch_concurrency: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Service configuration.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
/// Alias chosen by the operator.
|
||||
/// Doesn't have to be unique on the network.
|
||||
pub alias: Option<String>,
|
||||
/// Peers to connect to on startup.
|
||||
/// Connections to these peers will be maintained.
|
||||
pub connect: Vec<(NodeId, Address)>,
|
||||
/// Specify the node's public addresses
|
||||
pub external_addresses: Vec<Address>,
|
||||
/// Peer-to-peer network.
|
||||
pub network: Network,
|
||||
/// Whether or not our node should relay inventories.
|
||||
pub relay: bool,
|
||||
/// Configured service limits.
|
||||
pub limits: Limits,
|
||||
/// Default tracking policy.
|
||||
pub policy: Policy,
|
||||
/// Default tracking scope.
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
alias: None,
|
||||
connect: Vec::default(),
|
||||
external_addresses: vec![],
|
||||
network: Network::default(),
|
||||
relay: true,
|
||||
limits: Limits::default(),
|
||||
policy: Policy::default(),
|
||||
scope: Scope::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(network: Network) -> Self {
|
||||
Self {
|
||||
network,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer(&self, id: &NodeId) -> Option<&Address> {
|
||||
self.connect.iter().find(|(i, _)| i == id).map(|(_, a)| a)
|
||||
}
|
||||
|
||||
pub fn is_persistent(&self, id: &NodeId) -> bool {
|
||||
self.connect.iter().any(|(i, _)| i == id)
|
||||
}
|
||||
|
||||
pub fn features(&self) -> node::Features {
|
||||
node::Features::SEED
|
||||
}
|
||||
|
||||
/// Check if a node announcement matches this configuration.
|
||||
pub fn matches(&self, other: &NodeAnnouncement) -> bool {
|
||||
let ann = self.node(other.timestamp);
|
||||
|
||||
ann.features == other.features
|
||||
&& ann.alias == other.alias
|
||||
&& ann.addresses == other.addresses
|
||||
}
|
||||
|
||||
pub fn alias(&self) -> [u8; 32] {
|
||||
let mut alias = [0u8; 32];
|
||||
|
||||
if let Some(name) = &self.alias {
|
||||
alias[..name.len()].copy_from_slice(name.as_bytes());
|
||||
}
|
||||
alias
|
||||
}
|
||||
|
||||
pub fn node(&self, timestamp: node::Timestamp) -> NodeAnnouncement {
|
||||
let features = self.features();
|
||||
let alias = self.alias();
|
||||
let addresses: BoundedVec<_, ADDRESS_LIMIT> = self
|
||||
.external_addresses
|
||||
.clone()
|
||||
.try_into()
|
||||
.expect("external addresses are within the limit");
|
||||
|
||||
NodeAnnouncement {
|
||||
features,
|
||||
timestamp,
|
||||
alias,
|
||||
addresses,
|
||||
nonce: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_node_announcement() {
|
||||
let cfg = Config {
|
||||
alias: Some(String::from("cloudhead")),
|
||||
..Config::default()
|
||||
};
|
||||
assert_eq!("cloudhead", cfg.node(0).alias().unwrap());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{fmt, io, mem, str};
|
||||
use std::{fmt, io, mem};
|
||||
|
||||
use crate::crypto;
|
||||
use crate::crypto::Unverified;
|
||||
use crate::identity::Id;
|
||||
use crate::node;
|
||||
use crate::node::Address;
|
||||
use crate::node::{Address, Alias};
|
||||
use crate::prelude::BoundedVec;
|
||||
use crate::service::filter::Filter;
|
||||
use crate::service::{Link, NodeId, Timestamp};
|
||||
|
|
@ -57,8 +57,8 @@ pub struct NodeAnnouncement {
|
|||
pub features: node::Features,
|
||||
/// Monotonic timestamp.
|
||||
pub timestamp: Timestamp,
|
||||
/// Non-unique alias. Must be valid UTF-8.
|
||||
pub alias: [u8; 32],
|
||||
/// Non-unique alias.
|
||||
pub alias: Alias,
|
||||
/// Announced addresses.
|
||||
pub addresses: BoundedVec<Address, ADDRESS_LIMIT>,
|
||||
/// Nonce used for announcement proof-of-work.
|
||||
|
|
@ -118,11 +118,6 @@ impl NodeAnnouncement {
|
|||
}
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Get the alias as a UTF-8 string.
|
||||
pub fn alias(&self) -> Result<&str, std::str::Utf8Error> {
|
||||
Ok(str::from_utf8(&self.alias)?.trim_end_matches(0 as char))
|
||||
}
|
||||
}
|
||||
|
||||
impl wire::Encode for NodeAnnouncement {
|
||||
|
|
@ -619,7 +614,7 @@ mod tests {
|
|||
let ann = NodeAnnouncement {
|
||||
features: node::Features::SEED,
|
||||
timestamp: 42491841,
|
||||
alias: [0; 32],
|
||||
alias: Alias::new("alice"),
|
||||
addresses: BoundedVec::new(),
|
||||
nonce: 0,
|
||||
};
|
||||
|
|
@ -627,6 +622,6 @@ mod tests {
|
|||
assert_eq!(ann.work(), 0);
|
||||
assert_eq!(ann.clone().solve(1).unwrap().work(), 4);
|
||||
assert_eq!(ann.clone().solve(8).unwrap().work(), 9);
|
||||
assert_eq!(ann.solve(14).unwrap().work(), 16);
|
||||
assert_eq!(ann.solve(14).unwrap().work(), 14);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::{HashSet, VecDeque};
|
||||
use std::fmt;
|
||||
|
||||
use crate::service::config::Limits;
|
||||
use crate::node::config::Limits;
|
||||
use crate::service::message;
|
||||
use crate::service::message::Message;
|
||||
use crate::service::{Address, Id, LocalTime, NodeId, Outbox, Rng};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use bloomy::BloomFilter;
|
|||
use qcheck::Arbitrary;
|
||||
|
||||
use crate::crypto;
|
||||
use crate::node::Alias;
|
||||
use crate::prelude::{BoundedVec, Id, NodeId, Timestamp};
|
||||
use crate::service::filter::{Filter, FILTER_SIZE_L, FILTER_SIZE_M, FILTER_SIZE_S};
|
||||
use crate::service::message::{
|
||||
|
|
@ -65,7 +66,7 @@ impl Arbitrary for Message {
|
|||
let message = NodeAnnouncement {
|
||||
features: u64::arbitrary(g).into(),
|
||||
timestamp: Timestamp::arbitrary(g),
|
||||
alias: <[u8; 32]>::arbitrary(g),
|
||||
alias: Alias::arbitrary(g),
|
||||
addresses: Arbitrary::arbitrary(g),
|
||||
nonce: u64::arbitrary(g),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::io::BufRead as _;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
env, fs, io, iter, net, process, thread, time,
|
||||
|
|
@ -21,7 +22,8 @@ use radicle::node::address::Book;
|
|||
use radicle::node::routing::Store;
|
||||
use radicle::node::tracking::store as TrackingStore;
|
||||
use radicle::node::Handle as _;
|
||||
use radicle::node::{ADDRESS_DB_FILE, TRACKING_DB_FILE};
|
||||
use radicle::node::{Alias, ADDRESS_DB_FILE, TRACKING_DB_FILE};
|
||||
use radicle::profile;
|
||||
use radicle::profile::Home;
|
||||
use radicle::profile::Profile;
|
||||
use radicle::rad;
|
||||
|
|
@ -74,8 +76,8 @@ impl Environment {
|
|||
|
||||
/// Create a new node in this environment. This should be used when a running node
|
||||
/// is required. Use [`Environment::profile`] otherwise.
|
||||
pub fn node(&mut self, name: &str) -> Node<MemorySigner> {
|
||||
let profile = self.profile(name);
|
||||
pub fn node(&mut self, config: Config) -> Node<MemorySigner> {
|
||||
let profile = self.profile(&config.alias);
|
||||
let signer = MemorySigner::load(&profile.keystore, "radicle".to_owned().into()).unwrap();
|
||||
let tracking_db = profile.home.node().join(TRACKING_DB_FILE);
|
||||
TrackingStore::Config::open(tracking_db).unwrap();
|
||||
|
|
@ -85,6 +87,7 @@ impl Environment {
|
|||
Node {
|
||||
id: *profile.id(),
|
||||
home: profile.home,
|
||||
config,
|
||||
signer,
|
||||
storage: profile.storage,
|
||||
}
|
||||
|
|
@ -92,12 +95,15 @@ impl Environment {
|
|||
|
||||
/// Create a new profile in this environment.
|
||||
/// This should be used when a running node is not required.
|
||||
pub fn profile(&mut self, name: &str) -> Profile {
|
||||
let home = Home::new(self.tmp().join("home").join(name).join(".radicle")).unwrap();
|
||||
pub fn profile(&mut self, alias: &str) -> Profile {
|
||||
let home = Home::new(self.tmp().join("home").join(alias).join(".radicle")).unwrap();
|
||||
let storage = Storage::open(home.storage()).unwrap();
|
||||
let keystore = Keystore::new(&home.keys());
|
||||
let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32]));
|
||||
let tracking_db = home.node().join(TRACKING_DB_FILE);
|
||||
let alias = Alias::from_str(alias).unwrap();
|
||||
let config = profile::Config::init(alias, &home.config()).unwrap();
|
||||
|
||||
TrackingStore::Config::open(tracking_db).unwrap();
|
||||
let addresses_db = home.node().join(ADDRESS_DB_FILE);
|
||||
Book::open(addresses_db).unwrap();
|
||||
|
|
@ -115,6 +121,7 @@ impl Environment {
|
|||
storage,
|
||||
keystore,
|
||||
public_key: keypair.pk.into(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,6 +132,7 @@ pub struct Node<G> {
|
|||
pub home: Home,
|
||||
pub signer: G,
|
||||
pub storage: Storage,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
/// Handle to a running node.
|
||||
|
|
@ -303,7 +311,7 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
|
|||
|
||||
impl Node<MockSigner> {
|
||||
/// Create a new node.
|
||||
pub fn init(base: &Path) -> Self {
|
||||
pub fn init(base: &Path, config: Config) -> Self {
|
||||
let home = base.join(
|
||||
iter::repeat_with(fastrand::alphanumeric)
|
||||
.take(8)
|
||||
|
|
@ -318,13 +326,14 @@ impl Node<MockSigner> {
|
|||
home,
|
||||
signer,
|
||||
storage,
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
|
||||
/// Spawn a node in its own thread.
|
||||
pub fn spawn(self, config: service::Config) -> NodeHandle<G> {
|
||||
pub fn spawn(self) -> NodeHandle<G> {
|
||||
let listen = vec![([0, 0, 0, 0], 0).into()];
|
||||
let proxy = net::SocketAddr::new(net::Ipv4Addr::LOCALHOST.into(), 9050);
|
||||
let daemon: net::SocketAddr = {
|
||||
|
|
@ -337,7 +346,7 @@ impl<G: cyphernet::Ecdh<Pk = NodeId> + Signer + Clone> Node<G> {
|
|||
let (_, signals) = chan::bounded(1);
|
||||
let rt = Runtime::init(
|
||||
self.home.clone(),
|
||||
config,
|
||||
self.config,
|
||||
listen,
|
||||
proxy,
|
||||
daemon,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
|
|||
use std::{io, time};
|
||||
|
||||
use crate::identity::Id;
|
||||
use crate::node::{Event, FetchResult, Seeds};
|
||||
use crate::node::{Alias, Event, FetchResult, Seeds};
|
||||
use crate::runtime::HandleError;
|
||||
use crate::service::tracking;
|
||||
use crate::service::NodeId;
|
||||
|
|
@ -51,7 +51,7 @@ impl radicle::node::Handle for Handle {
|
|||
Ok(self.tracking_repos.lock().unwrap().remove(&id))
|
||||
}
|
||||
|
||||
fn track_node(&mut self, id: NodeId, _alias: Option<String>) -> Result<bool, Self::Error> {
|
||||
fn track_node(&mut self, id: NodeId, _alias: Option<Alias>) -> Result<bool, Self::Error> {
|
||||
Ok(self.tracking_nodes.lock().unwrap().insert(id))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
use std::iter;
|
||||
use std::net;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
use log::*;
|
||||
|
||||
use radicle::node::address;
|
||||
use radicle::node::address::Store;
|
||||
use radicle::node::{address, Alias};
|
||||
use radicle::rad;
|
||||
use radicle::storage::ReadRepository;
|
||||
use radicle::Storage;
|
||||
|
|
@ -110,7 +111,7 @@ impl Default for Config<MockSigner> {
|
|||
let signer = MockSigner::new(&mut rng);
|
||||
|
||||
Config {
|
||||
config: service::Config::default(),
|
||||
config: service::Config::new(Alias::from_str("mocky").unwrap()),
|
||||
addrs: address::Book::memory().unwrap(),
|
||||
local_time: LocalTime::now(),
|
||||
policy: Policy::default(),
|
||||
|
|
@ -162,7 +163,7 @@ where
|
|||
// Make sure the peer address is advertized.
|
||||
config.config.external_addresses.push(local_addr.into());
|
||||
|
||||
let announcement = config.config.node(config.local_time.as_secs());
|
||||
let announcement = service::gossip::node(&config.config, config.local_time.as_secs());
|
||||
let emitter: Emitter<Event> = Default::default();
|
||||
let service = Service::new(
|
||||
config.config,
|
||||
|
|
@ -214,7 +215,7 @@ where
|
|||
.insert(
|
||||
&peer.node_id(),
|
||||
radicle::node::Features::default(),
|
||||
peer.name,
|
||||
Alias::from_str(peer.name).unwrap(),
|
||||
0,
|
||||
timestamp,
|
||||
Some(known_address),
|
||||
|
|
@ -258,14 +259,11 @@ where
|
|||
}
|
||||
|
||||
pub fn node_announcement(&self) -> Message {
|
||||
let mut alias = [0u8; 32];
|
||||
alias[..self.name.len()].copy_from_slice(self.name.as_bytes());
|
||||
|
||||
Message::node(
|
||||
NodeAnnouncement {
|
||||
features: node::Features::SEED,
|
||||
timestamp: self.timestamp(),
|
||||
alias,
|
||||
alias: Alias::from_str(self.name).unwrap(),
|
||||
addresses: Some(net::SocketAddr::from((self.ip, node::DEFAULT_PORT)).into()).into(),
|
||||
nonce: 0,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ use crate::collections::{HashMap, HashSet};
|
|||
use crate::crypto::test::signer::MockSigner;
|
||||
use crate::identity::Id;
|
||||
use crate::node;
|
||||
use crate::node::config::*;
|
||||
use crate::prelude::*;
|
||||
use crate::prelude::{LocalDuration, Timestamp};
|
||||
use crate::service::config::*;
|
||||
use crate::service::filter::Filter;
|
||||
use crate::service::io::Io;
|
||||
use crate::service::message::*;
|
||||
|
|
@ -208,8 +208,11 @@ fn test_persistent_peer_connect() {
|
|||
MockStorage::empty(),
|
||||
peer::Config {
|
||||
config: Config {
|
||||
connect: vec![(bob.id(), bob.address()), (eve.id(), eve.address())],
|
||||
..Config::default()
|
||||
connect: vec![
|
||||
(bob.id(), bob.address()).into(),
|
||||
(eve.id(), eve.address()).into(),
|
||||
],
|
||||
..Config::new(node::Alias::new("alice"))
|
||||
},
|
||||
..peer::Config::default()
|
||||
},
|
||||
|
|
@ -320,7 +323,7 @@ fn test_inventory_pruning() {
|
|||
peer::Config {
|
||||
config: Config {
|
||||
limits: test.limits,
|
||||
..Config::default()
|
||||
..Config::new(node::Alias::new("alice"))
|
||||
},
|
||||
..peer::Config::default()
|
||||
},
|
||||
|
|
@ -753,7 +756,11 @@ fn test_refs_announcement_trusted() {
|
|||
|
||||
// Alice starts to track Bob.
|
||||
let (sender, receiver) = chan::bounded(1);
|
||||
alice.command(Command::TrackNode(bob.id, Some("bob".to_string()), sender));
|
||||
alice.command(Command::TrackNode(
|
||||
bob.id,
|
||||
Some(node::Alias::new("bob")),
|
||||
sender,
|
||||
));
|
||||
let policy_change = receiver.recv().map_err(runtime::HandleError::from).unwrap();
|
||||
assert!(policy_change);
|
||||
|
||||
|
|
@ -886,8 +893,11 @@ fn test_persistent_peer_reconnect_attempt() {
|
|||
MockStorage::empty(),
|
||||
peer::Config {
|
||||
config: Config {
|
||||
connect: vec![(bob.id(), bob.address()), (eve.id(), eve.address())],
|
||||
..Config::default()
|
||||
connect: vec![
|
||||
(bob.id(), bob.address()).into(),
|
||||
(eve.id(), eve.address()).into(),
|
||||
],
|
||||
..Config::new(node::Alias::new("alice"))
|
||||
},
|
||||
..peer::Config::default()
|
||||
},
|
||||
|
|
@ -944,8 +954,8 @@ fn test_persistent_peer_reconnect_success() {
|
|||
MockStorage::empty(),
|
||||
peer::Config {
|
||||
config: Config {
|
||||
connect: vec![(bob.id, bob.addr())],
|
||||
..Config::default()
|
||||
connect: vec![(bob.id, bob.addr()).into()],
|
||||
..Config::new(node::Alias::new("alice"))
|
||||
},
|
||||
..peer::Config::default()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ use std::{collections::HashSet, thread, time};
|
|||
|
||||
use radicle::crypto::{test::signer::MockSigner, Signer};
|
||||
use radicle::git;
|
||||
use radicle::node::{FetchResult, Handle as _};
|
||||
use radicle::node::{Alias, FetchResult, Handle as _};
|
||||
use radicle::storage::{ReadRepository, ReadStorage, WriteRepository, WriteStorage};
|
||||
use radicle::test::fixtures;
|
||||
use radicle::{assert_matches, rad};
|
||||
|
||||
use crate::node::config::Limits;
|
||||
use crate::node::Config;
|
||||
use crate::service;
|
||||
use crate::service::config::Limits;
|
||||
use crate::service::tracking::Scope;
|
||||
use crate::storage::git::transport;
|
||||
use crate::test::environment::{converge, Environment, Node};
|
||||
|
|
@ -23,14 +24,14 @@ fn test_inventory_sync_basic() {
|
|||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
|
||||
alice.project("alice", "");
|
||||
bob.project("bob", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
|
||||
|
|
@ -47,17 +48,17 @@ fn test_inventory_sync_bridge() {
|
|||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let mut eve = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let mut eve = Node::init(tmp.path(), Config::new(Alias::new("eve")));
|
||||
|
||||
alice.project("alice", "");
|
||||
bob.project("bob", "");
|
||||
eve.project("eve", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let eve = eve.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let eve = eve.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
bob.connect(&eve);
|
||||
|
|
@ -77,20 +78,20 @@ fn test_inventory_sync_ring() {
|
|||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let mut eve = Node::init(tmp.path());
|
||||
let mut carol = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let mut eve = Node::init(tmp.path(), Config::new(Alias::new("eve")));
|
||||
let mut carol = Node::init(tmp.path(), Config::new(Alias::new("carol")));
|
||||
|
||||
alice.project("alice", "");
|
||||
bob.project("bob", "");
|
||||
eve.project("eve", "");
|
||||
carol.project("carol", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut eve = eve.spawn(service::Config::default());
|
||||
let mut carol = carol.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
let mut carol = carol.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
bob.connect(&eve);
|
||||
|
|
@ -114,11 +115,11 @@ fn test_inventory_sync_star() {
|
|||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let mut eve = Node::init(tmp.path());
|
||||
let mut carol = Node::init(tmp.path());
|
||||
let mut dave = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let mut eve = Node::init(tmp.path(), Config::new(Alias::new("eve")));
|
||||
let mut carol = Node::init(tmp.path(), Config::new(Alias::new("carol")));
|
||||
let mut dave = Node::init(tmp.path(), Config::new(Alias::new("dave")));
|
||||
|
||||
alice.project("alice", "");
|
||||
bob.project("bob", "");
|
||||
|
|
@ -126,11 +127,11 @@ fn test_inventory_sync_star() {
|
|||
carol.project("carol", "");
|
||||
dave.project("dave", "");
|
||||
|
||||
let alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut eve = eve.spawn(service::Config::default());
|
||||
let mut carol = carol.spawn(service::Config::default());
|
||||
let mut dave = dave.spawn(service::Config::default());
|
||||
let alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
let mut carol = carol.spawn();
|
||||
let mut dave = dave.spawn();
|
||||
|
||||
bob.connect(&alice);
|
||||
eve.connect(&alice);
|
||||
|
|
@ -146,12 +147,12 @@ fn test_replication() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = bob.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -203,8 +204,8 @@ fn test_replication_no_delegates() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
|
||||
let acme = bob.project("acme", "");
|
||||
// Delete one of the signed refs.
|
||||
|
|
@ -216,8 +217,8 @@ fn test_replication_no_delegates() {
|
|||
.delete()
|
||||
.unwrap();
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -236,8 +237,8 @@ fn test_replication_no_delegates() {
|
|||
#[test]
|
||||
fn test_replication_invalid() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let carol = MockSigner::default();
|
||||
let acme = bob.project("acme", "");
|
||||
let repo = bob.storage.repository_mut(acme).unwrap();
|
||||
|
|
@ -262,8 +263,8 @@ fn test_replication_invalid() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -289,12 +290,12 @@ fn test_migrated_clone() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = alice.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -339,12 +340,12 @@ fn test_dont_fetch_owned_refs() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = alice.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -366,8 +367,8 @@ fn test_fetch_trusted_remotes() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = alice.project("acme", "");
|
||||
let mut signers = Vec::with_capacity(5);
|
||||
{
|
||||
|
|
@ -378,8 +379,8 @@ fn test_fetch_trusted_remotes() {
|
|||
}
|
||||
}
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -421,12 +422,12 @@ fn test_missing_remote() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = alice.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let carol = MockSigner::default();
|
||||
|
||||
alice.connect(&bob);
|
||||
|
|
@ -450,11 +451,11 @@ fn test_fetch_preserve_owned_refs() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = alice.project("acme", "");
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -496,12 +497,12 @@ fn test_clone() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = bob.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -554,12 +555,12 @@ fn test_fetch_up_to_date() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let acme = bob.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
|
||||
alice.connect(&bob);
|
||||
converge([&alice, &bob]);
|
||||
|
|
@ -584,8 +585,8 @@ fn test_large_fetch() {
|
|||
|
||||
let env = Environment::new();
|
||||
let scale = env.scale();
|
||||
let mut alice = Node::init(&env.tmp());
|
||||
let bob = Node::init(&env.tmp());
|
||||
let mut alice = Node::init(&env.tmp(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(&env.tmp(), Config::new(Alias::new("bob")));
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let (repo, _) = fixtures::repository(tmp.path());
|
||||
|
|
@ -593,8 +594,8 @@ fn test_large_fetch() {
|
|||
|
||||
let rid = alice.project_from("acme", "", &repo);
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let bob_events = bob.handle.events();
|
||||
|
||||
bob.handle.track_repo(rid, Scope::All).unwrap();
|
||||
|
|
@ -619,11 +620,28 @@ fn test_concurrent_fetches() {
|
|||
|
||||
let env = Environment::new();
|
||||
let scale = env.scale();
|
||||
let repos = scale.max(4);
|
||||
let limits = Limits {
|
||||
// Have one fetch be queued.
|
||||
fetch_concurrency: repos - 1,
|
||||
..Limits::default()
|
||||
};
|
||||
let mut bob_repos = HashSet::new();
|
||||
let mut alice_repos = HashSet::new();
|
||||
let mut alice = Node::init(&env.tmp());
|
||||
let mut bob = Node::init(&env.tmp());
|
||||
let repos = scale.max(4);
|
||||
let mut alice = Node::init(
|
||||
&env.tmp(),
|
||||
service::Config {
|
||||
limits: limits.clone(),
|
||||
..service::Config::new(Alias::new("alice"))
|
||||
},
|
||||
);
|
||||
let mut bob = Node::init(
|
||||
&env.tmp(),
|
||||
service::Config {
|
||||
limits,
|
||||
..service::Config::new(Alias::new("bob"))
|
||||
},
|
||||
);
|
||||
|
||||
for i in 0..repos {
|
||||
// Create a repo for Alice.
|
||||
|
|
@ -643,16 +661,8 @@ fn test_concurrent_fetches() {
|
|||
bob_repos.insert(rid);
|
||||
}
|
||||
|
||||
let config = service::Config {
|
||||
limits: Limits {
|
||||
// Have one fetch be queued.
|
||||
fetch_concurrency: repos - 1,
|
||||
..Limits::default()
|
||||
},
|
||||
..service::Config::default()
|
||||
};
|
||||
let mut alice = alice.spawn(config.clone());
|
||||
let mut bob = bob.spawn(config);
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
let alice_events = alice.handle.events();
|
||||
let bob_events = bob.handle.events();
|
||||
|
|
@ -718,11 +728,11 @@ fn test_connection_crossing() {
|
|||
logger::init(log::Level::Debug);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
|
||||
alice.handle.connect(bob.id, bob.addr.into()).unwrap();
|
||||
bob.handle.connect(alice.id, alice.addr.into()).unwrap();
|
||||
|
|
@ -753,15 +763,15 @@ fn test_non_fastforward_sigrefs() {
|
|||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
let alice = Node::init(tmp.path());
|
||||
let mut bob = Node::init(tmp.path());
|
||||
let eve = Node::init(tmp.path());
|
||||
let alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let mut bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let eve = Node::init(tmp.path(), Config::new(Alias::new("eve")));
|
||||
|
||||
let rid = bob.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let bob = bob.spawn(service::Config::default());
|
||||
let mut eve = eve.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
|
||||
alice.handle.track_repo(rid, Scope::All).unwrap();
|
||||
eve.handle.track_repo(rid, Scope::All).unwrap();
|
||||
|
|
@ -805,15 +815,15 @@ fn test_outdated_sigrefs() {
|
|||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
let mut alice = Node::init(tmp.path());
|
||||
let bob = Node::init(tmp.path());
|
||||
let eve = Node::init(tmp.path());
|
||||
let mut alice = Node::init(tmp.path(), Config::new(Alias::new("alice")));
|
||||
let bob = Node::init(tmp.path(), Config::new(Alias::new("bob")));
|
||||
let eve = Node::init(tmp.path(), Config::new(Alias::new("eve")));
|
||||
|
||||
let rid = alice.project("acme", "");
|
||||
|
||||
let mut alice = alice.spawn(service::Config::default());
|
||||
let mut bob = bob.spawn(service::Config::default());
|
||||
let mut eve = eve.spawn(service::Config::default());
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
let mut eve = eve.spawn();
|
||||
|
||||
bob.handle.track_repo(rid, Scope::All).unwrap();
|
||||
eve.handle.track_repo(rid, Scope::All).unwrap();
|
||||
|
|
@ -832,7 +842,7 @@ fn test_outdated_sigrefs() {
|
|||
|
||||
alice
|
||||
.handle
|
||||
.track_node(eve.id, Some("eve".to_string()))
|
||||
.track_node(eve.id, Some(Alias::new("eve")))
|
||||
.unwrap();
|
||||
alice.handle.fetch(rid, eve.id).unwrap();
|
||||
let repo = alice.storage.repository(rid).unwrap();
|
||||
|
|
@ -873,7 +883,7 @@ fn test_outdated_sigrefs() {
|
|||
|
||||
alice
|
||||
.handle
|
||||
.track_node(bob.id, Some("bob".to_string()))
|
||||
.track_node(bob.id, Some(Alias::new("bob")))
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
alice.handle.fetch(rid, bob.id).unwrap(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub use protocol::{Control, Wire, WireReader, WireSession, WireWriter};
|
|||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::{io, mem};
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ use crate::git;
|
|||
use crate::git::fmt;
|
||||
use crate::identity::Id;
|
||||
use crate::node;
|
||||
use crate::node::Alias;
|
||||
use crate::prelude::*;
|
||||
use crate::service::filter;
|
||||
use crate::storage::refs::Refs;
|
||||
|
|
@ -47,6 +49,8 @@ pub enum Error {
|
|||
InvalidStreamKind(u8),
|
||||
#[error(transparent)]
|
||||
InvalidRefName(#[from] fmt::Error),
|
||||
#[error(transparent)]
|
||||
InvalidAlias(#[from] node::AliasError),
|
||||
#[error("invalid control message with type `{0}`")]
|
||||
InvalidControlMessage(u8),
|
||||
#[error("invalid protocol version header `{0:x?}`")]
|
||||
|
|
@ -219,6 +223,12 @@ impl Encode for Refs {
|
|||
}
|
||||
}
|
||||
|
||||
impl Encode for Alias {
|
||||
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||
self.as_ref().encode(writer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Encode for (A, B)
|
||||
where
|
||||
A: Encode,
|
||||
|
|
@ -284,6 +294,12 @@ impl Decode for git::RefString {
|
|||
}
|
||||
}
|
||||
|
||||
impl Decode for Alias {
|
||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||
String::decode(reader).and_then(|s| Alias::from_str(&s).map_err(Error::from))
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Decode for (A, B)
|
||||
where
|
||||
A: Decode,
|
||||
|
|
@ -573,6 +589,14 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias() {
|
||||
assert_eq!(
|
||||
serialize(&Alias::from_str("hello").unwrap()),
|
||||
vec![5, b'h', b'e', b'l', b'l', b'o']
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_invalid() {
|
||||
let b = bloomy::BloomFilter::with_size(filter::FILTER_SIZE_M / 3);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ pub static CONFIG: Lazy<RenderConfig> = Lazy::new(|| RenderConfig {
|
|||
answer: StyleSheet::new(),
|
||||
highlighted_option_prefix: Styled::new("*").with_fg(Color::LightYellow),
|
||||
help_message: StyleSheet::new().with_fg(Color::DarkGrey),
|
||||
default_value: StyleSheet::new().with_fg(Color::LightBlue),
|
||||
error_message: ErrorMessageRenderConfig::default_colored()
|
||||
.with_prefix(Styled::new("✗").with_fg(Color::LightRed)),
|
||||
..RenderConfig::default_colored()
|
||||
|
|
@ -173,16 +174,18 @@ pub fn abort<D: fmt::Display>(prompt: D) -> bool {
|
|||
ask(prompt, false)
|
||||
}
|
||||
|
||||
pub fn input<S, E>(message: &str, default: Option<S>) -> anyhow::Result<S>
|
||||
pub fn input<S, E>(message: &str, default: Option<S>, help: Option<&str>) -> anyhow::Result<S>
|
||||
where
|
||||
S: fmt::Display + std::str::FromStr<Err = E> + Clone,
|
||||
E: fmt::Debug + fmt::Display,
|
||||
{
|
||||
let input = CustomType::<S>::new(message).with_render_config(*CONFIG);
|
||||
let value = match default {
|
||||
Some(default) => input.with_default(default).prompt()?,
|
||||
None => input.prompt()?,
|
||||
};
|
||||
let mut input = CustomType::<S>::new(message).with_render_config(*CONFIG);
|
||||
|
||||
input.default = default;
|
||||
input.help_message = help;
|
||||
|
||||
let value = input.prompt()?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ path = "../radicle-cli"
|
|||
name = "rad-init"
|
||||
path = "src/rad-init.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rad-auth"
|
||||
path = "src/rad-auth.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rad-self"
|
||||
path = "src/rad-self.rs"
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
use radicle::profile;
|
||||
use radicle::profile::{Error, Profile};
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let profile = match Profile::load() {
|
||||
Ok(profile) => profile,
|
||||
Err(Error::NotFound(_)) => Profile::init(profile::home()?, "radicle".to_owned())?,
|
||||
Err(err) => anyhow::bail!(err),
|
||||
};
|
||||
|
||||
println!("id: {}", profile.id());
|
||||
println!("home: {}", profile.home().display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ pub mod prelude {
|
|||
|
||||
pub use crypto::{PublicKey, Signer, Verified};
|
||||
pub use identity::{project::Project, Did, Doc, Id};
|
||||
pub use node::{NodeId, Timestamp};
|
||||
pub use node::{Alias, NodeId, Timestamp};
|
||||
pub use profile::Profile;
|
||||
pub use storage::{BranchName, ReadRepository, ReadStorage, WriteRepository, WriteStorage};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod features;
|
||||
|
||||
pub mod address;
|
||||
pub mod config;
|
||||
pub mod events;
|
||||
pub mod routing;
|
||||
pub mod tracking;
|
||||
|
|
@ -10,6 +11,7 @@ use std::io::{BufRead, BufReader};
|
|||
use std::ops::Deref;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::{fmt, io, net, thread, time};
|
||||
|
||||
use amplify::WrapperMut;
|
||||
|
|
@ -23,6 +25,7 @@ use crate::crypto::PublicKey;
|
|||
use crate::identity::Id;
|
||||
use crate::storage::RefUpdate;
|
||||
|
||||
pub use config::Config;
|
||||
pub use cyphernet::addr::PeerAddr;
|
||||
pub use events::{Event, Events};
|
||||
pub use features::Features;
|
||||
|
|
@ -33,6 +36,8 @@ pub const DEFAULT_SOCKET_NAME: &str = "control.sock";
|
|||
pub const DEFAULT_PORT: u16 = 8776;
|
||||
/// Default timeout when waiting for the node to respond with data.
|
||||
pub const DEFAULT_TIMEOUT: time::Duration = time::Duration::from_secs(9);
|
||||
/// Maximum length in bytes of a node alias.
|
||||
pub const MAX_ALIAS_LENGTH: usize = 32;
|
||||
/// Filename of routing table database under the node directory.
|
||||
pub const ROUTING_DB_FILE: &str = "routing.db";
|
||||
/// Filename of address database under the node directory.
|
||||
|
|
@ -107,6 +112,90 @@ impl fmt::Display for State {
|
|||
}
|
||||
}
|
||||
|
||||
/// Node alias.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Alias(String);
|
||||
|
||||
impl Alias {
|
||||
/// Create a new alias from a string. Panics if the string is not a valid alias.
|
||||
pub fn new(alias: impl ToString) -> Self {
|
||||
let alias = alias.to_string();
|
||||
|
||||
match Self::from_str(&alias) {
|
||||
Ok(a) => a,
|
||||
Err(e) => panic!("Alias::new: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Alias> for String {
|
||||
fn from(value: Alias) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&NodeId> for Alias {
|
||||
fn from(nid: &NodeId) -> Self {
|
||||
Alias(nid.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Alias {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Alias {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Alias {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Alias> for [u8; 32] {
|
||||
fn from(input: &Alias) -> [u8; 32] {
|
||||
let mut alias = [0u8; 32];
|
||||
|
||||
alias[..input.len()].copy_from_slice(input.as_bytes());
|
||||
alias
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum AliasError {
|
||||
#[error("alias cannot be empty")]
|
||||
Empty,
|
||||
#[error("alias cannot be greater than {MAX_ALIAS_LENGTH} bytes")]
|
||||
MaxBytesExceeded,
|
||||
#[error("alias cannot contain whitespace or control characters")]
|
||||
InvalidCharacter,
|
||||
}
|
||||
|
||||
impl FromStr for Alias {
|
||||
type Err = AliasError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.is_empty() {
|
||||
return Err(AliasError::Empty);
|
||||
}
|
||||
if s.chars().any(|c| c.is_control() || c.is_whitespace()) {
|
||||
return Err(AliasError::InvalidCharacter);
|
||||
}
|
||||
if s.len() > MAX_ALIAS_LENGTH {
|
||||
return Err(AliasError::MaxBytesExceeded);
|
||||
}
|
||||
Ok(Self(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a command, on the node control socket.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "status")]
|
||||
|
|
@ -499,7 +588,7 @@ pub trait Handle: Clone + Sync + Send {
|
|||
/// tracked.
|
||||
fn track_repo(&mut self, id: Id, scope: tracking::Scope) -> Result<bool, Self::Error>;
|
||||
/// Start tracking the given node.
|
||||
fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Self::Error>;
|
||||
fn track_node(&mut self, id: NodeId, alias: Option<Alias>) -> Result<bool, Self::Error>;
|
||||
/// Untrack the given project and delete it from storage.
|
||||
fn untrack_repo(&mut self, id: Id) -> Result<bool, Self::Error>;
|
||||
/// Untrack the given node.
|
||||
|
|
@ -671,7 +760,7 @@ impl Handle for Node {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
fn track_node(&mut self, id: NodeId, alias: Option<String>) -> Result<bool, Error> {
|
||||
fn track_node(&mut self, id: NodeId, alias: Option<Alias>) -> Result<bool, Error> {
|
||||
let id = id.to_human();
|
||||
let args = if let Some(alias) = alias.as_deref() {
|
||||
vec![id.as_str(), alias]
|
||||
|
|
@ -787,23 +876,23 @@ impl Handle for Node {
|
|||
/// A trait for different sources which can potentially return an alias.
|
||||
pub trait AliasStore {
|
||||
/// Returns alias of a `NodeId`.
|
||||
fn alias(&self, nid: &NodeId) -> Option<String>;
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias>;
|
||||
}
|
||||
|
||||
impl<T: AliasStore + ?Sized> AliasStore for &T {
|
||||
fn alias(&self, nid: &NodeId) -> Option<String> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias> {
|
||||
(*self).alias(nid)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AliasStore + ?Sized> AliasStore for Box<T> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<String> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias> {
|
||||
self.deref().alias(nid)
|
||||
}
|
||||
}
|
||||
|
||||
impl AliasStore for HashMap<NodeId, String> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<String> {
|
||||
impl AliasStore for HashMap<NodeId, Alias> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias> {
|
||||
self.get(nid).map(ToOwned::to_owned)
|
||||
}
|
||||
}
|
||||
|
|
@ -816,4 +905,19 @@ mod test {
|
|||
fn test_command_name_display() {
|
||||
assert_eq!(CommandName::TrackNode.to_string(), "track-node");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alias() {
|
||||
assert!(Alias::from_str("cloudhead").is_ok());
|
||||
assert!(Alias::from_str("cloud-head").is_ok());
|
||||
assert!(Alias::from_str("cl0ud.h3ad$__").is_ok());
|
||||
assert!(Alias::from_str("©loudhèâd").is_ok());
|
||||
|
||||
assert!(Alias::from_str("").is_err());
|
||||
assert!(Alias::from_str(" ").is_err());
|
||||
assert!(Alias::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").is_err());
|
||||
assert!(Alias::from_str("cloud\0head").is_err());
|
||||
assert!(Alias::from_str("cloud head").is_err());
|
||||
assert!(Alias::from_str("cloudhead\n").is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ create table if not exists "nodes" (
|
|||
-- Node features.
|
||||
"features" integer not null,
|
||||
-- Node alias.
|
||||
"alias" text default null,
|
||||
"alias" text not null,
|
||||
--- Node announcement proof-of-work.
|
||||
"pow" integer default 0,
|
||||
-- Node announcement timestamp.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::{fmt, io};
|
||||
|
||||
use localtime::LocalTime;
|
||||
|
|
@ -7,9 +8,7 @@ use thiserror::Error;
|
|||
|
||||
use crate::node;
|
||||
use crate::node::address::{KnownAddress, Source};
|
||||
use crate::node::Address;
|
||||
use crate::node::AliasStore;
|
||||
use crate::node::NodeId;
|
||||
use crate::node::{Address, Alias, AliasError, AliasStore, NodeId};
|
||||
use crate::prelude::Timestamp;
|
||||
use crate::sql::transaction;
|
||||
|
||||
|
|
@ -21,6 +20,8 @@ pub enum Error {
|
|||
/// I/O error.
|
||||
#[error("i/o error: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("alias error: {0}")]
|
||||
InvalidAlias(#[from] AliasError),
|
||||
/// An Internal error.
|
||||
#[error("internal error: {0}")]
|
||||
Internal(#[from] sql::Error),
|
||||
|
|
@ -77,7 +78,7 @@ impl Store for Book {
|
|||
|
||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||
let features = row.read::<node::Features, _>("features");
|
||||
let alias = row.read::<Option<&str>, _>("alias").map(ToOwned::to_owned);
|
||||
let alias = Alias::from_str(row.read::<&str, _>("alias"))?;
|
||||
let timestamp = row.read::<i64, _>("timestamp") as Timestamp;
|
||||
let pow = row.read::<i64, _>("pow") as u32;
|
||||
let mut addrs = Vec::new();
|
||||
|
|
@ -130,7 +131,7 @@ impl Store for Book {
|
|||
&mut self,
|
||||
node: &NodeId,
|
||||
features: node::Features,
|
||||
alias: &str,
|
||||
alias: Alias,
|
||||
pow: u32,
|
||||
timestamp: Timestamp,
|
||||
addrs: impl IntoIterator<Item = KnownAddress>,
|
||||
|
|
@ -143,15 +144,10 @@ impl Store for Book {
|
|||
SET features = ?2, alias = ?3, pow = ?4, timestamp = ?5
|
||||
WHERE timestamp < ?5",
|
||||
)?;
|
||||
let alias = if alias.is_empty() {
|
||||
sql::Value::Null
|
||||
} else {
|
||||
sql::Value::String(alias.to_owned())
|
||||
};
|
||||
|
||||
stmt.bind((1, node))?;
|
||||
stmt.bind((2, features))?;
|
||||
stmt.bind((3, alias))?;
|
||||
stmt.bind((3, sql::Value::String(alias.into())))?;
|
||||
stmt.bind((4, pow as i64))?;
|
||||
stmt.bind((5, timestamp as i64))?;
|
||||
stmt.next()?;
|
||||
|
|
@ -263,9 +259,9 @@ impl Store for Book {
|
|||
impl AliasStore for Book {
|
||||
/// Retrieve `alias` of given node.
|
||||
/// Calls `Self::get` under the hood.
|
||||
fn alias(&self, nid: &NodeId) -> Option<String> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias> {
|
||||
self.get(nid)
|
||||
.map(|node| node.and_then(|n| n.alias))
|
||||
.map(|node| node.map(|n| n.alias))
|
||||
.unwrap_or(None)
|
||||
}
|
||||
}
|
||||
|
|
@ -283,7 +279,7 @@ pub trait Store {
|
|||
&mut self,
|
||||
node: &NodeId,
|
||||
features: node::Features,
|
||||
alias: &str,
|
||||
alias: Alias,
|
||||
pow: u32,
|
||||
timestamp: Timestamp,
|
||||
addrs: impl IntoIterator<Item = KnownAddress>,
|
||||
|
|
@ -409,16 +405,16 @@ mod test {
|
|||
let timestamp = LocalTime::now().as_millis();
|
||||
|
||||
cache
|
||||
.insert(&alice, features, "alice", 16, timestamp, [])
|
||||
.insert(&alice, features, Alias::new("alice"), 16, timestamp, [])
|
||||
.unwrap();
|
||||
let node = cache.get(&alice).unwrap().unwrap();
|
||||
assert_eq!(node.alias.as_deref(), Some("alice"));
|
||||
assert_eq!(node.alias.as_ref(), "alice");
|
||||
|
||||
cache
|
||||
.insert(&alice, features, "", 16, timestamp + 1, [])
|
||||
.insert(&alice, features, Alias::new("bob"), 16, timestamp + 1, [])
|
||||
.unwrap();
|
||||
let node = cache.get(&alice).unwrap().unwrap();
|
||||
assert_eq!(node.alias.as_deref(), None);
|
||||
assert_eq!(node.alias.as_ref(), "bob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -435,7 +431,14 @@ mod test {
|
|||
last_attempt: None,
|
||||
};
|
||||
let inserted = cache
|
||||
.insert(&alice, features, "alice", 16, timestamp, [ka.clone()])
|
||||
.insert(
|
||||
&alice,
|
||||
features,
|
||||
Alias::new("alice"),
|
||||
16,
|
||||
timestamp,
|
||||
[ka.clone()],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(inserted);
|
||||
|
||||
|
|
@ -444,7 +447,7 @@ mod test {
|
|||
assert_eq!(node.features, features);
|
||||
assert_eq!(node.pow, 16);
|
||||
assert_eq!(node.timestamp, timestamp);
|
||||
assert_eq!(node.alias.as_deref(), Some("alice"));
|
||||
assert_eq!(node.alias.as_ref(), "alice");
|
||||
assert_eq!(node.addrs, vec![ka]);
|
||||
}
|
||||
|
||||
|
|
@ -454,6 +457,7 @@ mod test {
|
|||
let mut cache = Book::memory().unwrap();
|
||||
let features = node::Features::SEED;
|
||||
let timestamp = LocalTime::now().as_millis();
|
||||
let alias = Alias::new("alice");
|
||||
|
||||
let ka = KnownAddress {
|
||||
addr: net::SocketAddr::from(([4, 4, 4, 4], 8776)).into(),
|
||||
|
|
@ -462,12 +466,12 @@ mod test {
|
|||
last_attempt: None,
|
||||
};
|
||||
let inserted = cache
|
||||
.insert(&alice, features, "alice", 0, timestamp, [ka.clone()])
|
||||
.insert(&alice, features, alias.clone(), 0, timestamp, [ka.clone()])
|
||||
.unwrap();
|
||||
assert!(inserted);
|
||||
|
||||
let inserted = cache
|
||||
.insert(&alice, features, "alice", 0, timestamp, [ka])
|
||||
.insert(&alice, features, alias, 0, timestamp, [ka])
|
||||
.unwrap();
|
||||
assert!(!inserted);
|
||||
|
||||
|
|
@ -480,6 +484,8 @@ mod test {
|
|||
let mut cache = Book::memory().unwrap();
|
||||
let timestamp = LocalTime::now().as_millis();
|
||||
let features = node::Features::SEED;
|
||||
let alias1 = Alias::new("alice");
|
||||
let alias2 = Alias::new("~alice~");
|
||||
let ka = KnownAddress {
|
||||
addr: net::SocketAddr::from(([4, 4, 4, 4], 8776)).into(),
|
||||
source: Source::Peer,
|
||||
|
|
@ -488,45 +494,38 @@ mod test {
|
|||
};
|
||||
|
||||
let updated = cache
|
||||
.insert(&alice, features, "alice", 0, timestamp, [ka.clone()])
|
||||
.insert(&alice, features, alias1, 0, timestamp, [ka.clone()])
|
||||
.unwrap();
|
||||
assert!(updated);
|
||||
|
||||
let updated = cache
|
||||
.insert(&alice, features, "~alice~", 0, timestamp, [])
|
||||
.insert(&alice, features, alias2.clone(), 0, timestamp, [])
|
||||
.unwrap();
|
||||
assert!(!updated, "Can't update using the same timestamp");
|
||||
|
||||
let updated = cache
|
||||
.insert(&alice, features, "~alice~", 0, timestamp - 1, [])
|
||||
.insert(&alice, features, alias2.clone(), 0, timestamp - 1, [])
|
||||
.unwrap();
|
||||
assert!(!updated, "Can't update using a smaller timestamp");
|
||||
|
||||
let node = cache.get(&alice).unwrap().unwrap();
|
||||
assert_eq!(node.alias.as_deref(), Some("alice"));
|
||||
assert_eq!(node.alias.as_ref(), "alice");
|
||||
assert_eq!(node.timestamp, timestamp);
|
||||
assert_eq!(node.pow, 0);
|
||||
|
||||
let updated = cache
|
||||
.insert(&alice, features, "~alice~", 0, timestamp + 1, [])
|
||||
.insert(&alice, features, alias2.clone(), 0, timestamp + 1, [])
|
||||
.unwrap();
|
||||
assert!(updated, "Can update with a larger timestamp");
|
||||
|
||||
let updated = cache
|
||||
.insert(
|
||||
&alice,
|
||||
node::Features::NONE,
|
||||
"~alice~",
|
||||
1,
|
||||
timestamp + 2,
|
||||
[],
|
||||
)
|
||||
.insert(&alice, node::Features::NONE, alias2, 1, timestamp + 2, [])
|
||||
.unwrap();
|
||||
assert!(updated);
|
||||
|
||||
let node = cache.get(&alice).unwrap().unwrap();
|
||||
assert_eq!(node.features, node::Features::NONE);
|
||||
assert_eq!(node.alias.as_deref(), Some("~alice~"));
|
||||
assert_eq!(node.alias.as_ref(), "~alice~");
|
||||
assert_eq!(node.timestamp, timestamp + 2);
|
||||
assert_eq!(node.pow, 1);
|
||||
assert_eq!(node.addrs, vec![ka]);
|
||||
|
|
@ -539,6 +538,8 @@ mod test {
|
|||
let mut cache = Book::memory().unwrap();
|
||||
let timestamp = LocalTime::now().as_millis();
|
||||
let features = node::Features::SEED;
|
||||
let alice_alias = Alias::new("alice");
|
||||
let bob_alias = Alias::new("bob");
|
||||
|
||||
for addr in [
|
||||
([4, 4, 4, 4], 8776),
|
||||
|
|
@ -552,10 +553,17 @@ mod test {
|
|||
last_attempt: None,
|
||||
};
|
||||
cache
|
||||
.insert(&alice, features, "alice", 0, timestamp, [ka.clone()])
|
||||
.insert(
|
||||
&alice,
|
||||
features,
|
||||
alice_alias.clone(),
|
||||
0,
|
||||
timestamp,
|
||||
[ka.clone()],
|
||||
)
|
||||
.unwrap();
|
||||
cache
|
||||
.insert(&bob, features, "bob", 0, timestamp, [ka])
|
||||
.insert(&bob, features, bob_alias.clone(), 0, timestamp, [ka])
|
||||
.unwrap();
|
||||
}
|
||||
assert_eq!(cache.len().unwrap(), 6);
|
||||
|
|
@ -577,6 +585,7 @@ mod test {
|
|||
let mut expected = Vec::new();
|
||||
let timestamp = LocalTime::now().as_millis();
|
||||
let features = node::Features::SEED;
|
||||
let alias = Alias::new("alice");
|
||||
|
||||
for id in ids {
|
||||
let ip = rng.u32(..);
|
||||
|
|
@ -590,7 +599,7 @@ mod test {
|
|||
};
|
||||
expected.push((id, ka.clone()));
|
||||
cache
|
||||
.insert(&id, features, "alias", 0, timestamp, [ka])
|
||||
.insert(&id, features, alias.clone(), 0, timestamp, [ka])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use nonempty::NonEmpty;
|
|||
|
||||
use crate::collections::HashMap;
|
||||
use crate::node;
|
||||
use crate::node::Address;
|
||||
use crate::node::{Address, Alias};
|
||||
use crate::prelude::Timestamp;
|
||||
|
||||
/// A map with the ability to randomly select values.
|
||||
|
|
@ -78,7 +78,7 @@ impl<K, V> DerefMut for AddressBook<K, V> {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Node {
|
||||
/// Advertized alias.
|
||||
pub alias: Option<String>,
|
||||
pub alias: Alias,
|
||||
/// Advertized features.
|
||||
pub features: node::Features,
|
||||
/// Advertized addresses
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use cyphernet::addr::PeerAddr;
|
||||
use localtime::LocalDuration;
|
||||
|
||||
use crate::node;
|
||||
use crate::node::tracking::{Policy, Scope};
|
||||
use crate::node::{Address, Alias, NodeId};
|
||||
|
||||
/// Peer-to-peer network.
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Network {
|
||||
#[default]
|
||||
Main,
|
||||
Test,
|
||||
}
|
||||
|
||||
/// Configuration parameters defining attributes of minima and maxima.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Limits {
|
||||
/// Number of routing table entries before we start pruning.
|
||||
pub routing_max_size: usize,
|
||||
/// How long to keep a routing table entry before being pruned.
|
||||
#[serde(with = "crate::serde_ext::localtime::duration")]
|
||||
pub routing_max_age: LocalDuration,
|
||||
/// Maximum number of concurrent fetches per per connection.
|
||||
pub fetch_concurrency: usize,
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
routing_max_size: 1000,
|
||||
routing_max_age: LocalDuration::from_mins(7 * 24 * 60),
|
||||
fetch_concurrency: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Full address used to connect to a remote node.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ConnectAddress(#[serde(with = "crate::serde_ext::string")] PeerAddr<NodeId, Address>);
|
||||
|
||||
impl From<PeerAddr<NodeId, Address>> for ConnectAddress {
|
||||
fn from(value: PeerAddr<NodeId, Address>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConnectAddress> for (NodeId, Address) {
|
||||
fn from(value: ConnectAddress) -> Self {
|
||||
(value.0.id, value.0.addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(NodeId, Address)> for ConnectAddress {
|
||||
fn from((id, addr): (NodeId, Address)) -> Self {
|
||||
Self(PeerAddr { id, addr })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ConnectAddress {
|
||||
type Target = PeerAddr<NodeId, Address>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Service configuration.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
/// Node alias.
|
||||
pub alias: Alias,
|
||||
/// Peers to connect to on startup.
|
||||
/// Connections to these peers will be maintained.
|
||||
pub connect: Vec<ConnectAddress>,
|
||||
/// Specify the node's public addresses
|
||||
pub external_addresses: Vec<Address>,
|
||||
/// Peer-to-peer network.
|
||||
pub network: Network,
|
||||
/// Whether or not our node should relay inventories.
|
||||
pub relay: bool,
|
||||
/// Configured service limits.
|
||||
pub limits: Limits,
|
||||
/// Default tracking policy.
|
||||
pub policy: Policy,
|
||||
/// Default tracking scope.
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(alias: Alias) -> Self {
|
||||
Self {
|
||||
alias,
|
||||
connect: Vec::default(),
|
||||
external_addresses: vec![],
|
||||
network: Network::default(),
|
||||
relay: true,
|
||||
limits: Limits::default(),
|
||||
policy: Policy::default(),
|
||||
scope: Scope::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn peer(&self, id: &NodeId) -> Option<&Address> {
|
||||
self.connect
|
||||
.iter()
|
||||
.find(|ca| &ca.id == id)
|
||||
.map(|ca| &ca.addr)
|
||||
}
|
||||
|
||||
pub fn is_persistent(&self, id: &NodeId) -> bool {
|
||||
self.peer(id).is_some()
|
||||
}
|
||||
|
||||
pub fn features(&self) -> node::Features {
|
||||
node::Features::SEED
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ use thiserror::Error;
|
|||
|
||||
use crate::prelude::Id;
|
||||
|
||||
use super::NodeId;
|
||||
pub use super::{Alias, NodeId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Repo {
|
||||
|
|
@ -24,11 +24,9 @@ pub struct Node {
|
|||
pub policy: Policy,
|
||||
}
|
||||
|
||||
/// Node alias.
|
||||
pub type Alias = String;
|
||||
|
||||
/// Tracking policy.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Policy {
|
||||
/// The resource is tracked.
|
||||
Track,
|
||||
|
|
@ -91,6 +89,7 @@ impl TryFrom<&sqlite::Value> for Policy {
|
|||
|
||||
/// Tracking scope of a repository tracking policy.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Scope {
|
||||
/// Track remotes of nodes that are already tracked.
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#![allow(clippy::type_complexity)]
|
||||
use std::path::Path;
|
||||
use std::{fmt, io, ops::Not as _, time};
|
||||
use std::{fmt, io, ops::Not as _, str::FromStr, time};
|
||||
|
||||
use sqlite as sql;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::node::AliasStore;
|
||||
use crate::node::{Alias, AliasStore};
|
||||
use crate::prelude::{Id, NodeId};
|
||||
|
||||
use super::{Node, Policy, Repo, Scope};
|
||||
|
|
@ -188,7 +188,11 @@ impl Config {
|
|||
|
||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||
let alias = row.read::<&str, _>("alias");
|
||||
let alias = alias.is_empty().not().then_some(alias.to_owned());
|
||||
let alias = alias
|
||||
.is_empty()
|
||||
.not()
|
||||
.then_some(alias.to_owned())
|
||||
.and_then(|s| Alias::from_str(&s).ok());
|
||||
let policy = row.read::<Policy, _>("policy");
|
||||
|
||||
return Ok(Some(Node {
|
||||
|
|
@ -229,7 +233,11 @@ impl Config {
|
|||
while let Some(Ok(row)) = stmt.next() {
|
||||
let id = row.read("id");
|
||||
let alias = row.read::<&str, _>("alias").to_owned();
|
||||
let alias = alias.is_empty().not().then_some(alias.to_owned());
|
||||
let alias = alias
|
||||
.is_empty()
|
||||
.not()
|
||||
.then_some(alias.to_owned())
|
||||
.and_then(|s| Alias::from_str(&s).ok());
|
||||
let policy = row.read::<Policy, _>("policy");
|
||||
|
||||
entries.push(Node { id, alias, policy });
|
||||
|
|
@ -260,7 +268,7 @@ impl Config {
|
|||
impl AliasStore for Config {
|
||||
/// Retrieve `alias` of given node.
|
||||
/// Calls `Self::node_policy` under the hood.
|
||||
fn alias(&self, nid: &NodeId) -> Option<String> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias> {
|
||||
self.node_policy(nid)
|
||||
.map(|node| node.and_then(|n| n.alias))
|
||||
.unwrap_or(None)
|
||||
|
|
@ -334,7 +342,7 @@ mod test {
|
|||
assert!(db.track_node(&id, Some("eve")).unwrap());
|
||||
assert_eq!(
|
||||
db.node_policy(&id).unwrap().unwrap().alias,
|
||||
Some(String::from("eve"))
|
||||
Some(Alias::from_str("eve").unwrap())
|
||||
);
|
||||
assert!(db.track_node(&id, None).unwrap());
|
||||
assert_eq!(db.node_policy(&id).unwrap().unwrap().alias, None);
|
||||
|
|
@ -342,7 +350,7 @@ mod test {
|
|||
assert!(db.track_node(&id, Some("alice")).unwrap());
|
||||
assert_eq!(
|
||||
db.node_policy(&id).unwrap().unwrap().alias,
|
||||
Some(String::from("alice"))
|
||||
Some(Alias::new("alice"))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,18 @@
|
|||
//! node/
|
||||
//! control.sock # Node control socket
|
||||
//!
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
use std::{fs, io, str::FromStr};
|
||||
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::crypto::ssh::agent::Agent;
|
||||
use crate::crypto::ssh::{keystore, Keystore, Passphrase};
|
||||
use crate::crypto::{PublicKey, Signer};
|
||||
use crate::node;
|
||||
use crate::node::{address, routing, tracking, AliasStore};
|
||||
use crate::node::{address, routing, tracking, Alias, AliasStore};
|
||||
use crate::prelude::Did;
|
||||
use crate::prelude::NodeId;
|
||||
use crate::storage::git::transport;
|
||||
|
|
@ -49,10 +51,12 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
Config(#[from] ConfigError),
|
||||
#[error(transparent)]
|
||||
Keystore(#[from] keystore::Error),
|
||||
#[error(transparent)]
|
||||
MemorySigner(#[from] keystore::MemorySignerError),
|
||||
#[error("no profile found at the filepath '{0}'")]
|
||||
#[error("no profile found at path '{0}'")]
|
||||
NotFound(PathBuf),
|
||||
#[error("error connecting to ssh-agent: {0}")]
|
||||
Agent(#[from] crate::crypto::ssh::agent::Error),
|
||||
|
|
@ -64,19 +68,86 @@ pub enum Error {
|
|||
AddressStore(#[from] node::address::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("failed to load node configuration from {0}: {1}")]
|
||||
Io(PathBuf, io::Error),
|
||||
#[error("failed to decode node configuration from {0}: {1}")]
|
||||
Json(PathBuf, serde_json::Error),
|
||||
}
|
||||
|
||||
/// Local radicle configuration.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
pub node: node::Config,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Initialize a new configuration. Fails if the path already exists.
|
||||
pub fn init(alias: Alias, path: &Path) -> io::Result<Self> {
|
||||
let cfg = Self {
|
||||
node: node::Config::new(alias),
|
||||
};
|
||||
let mut file = fs::OpenOptions::new()
|
||||
.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);
|
||||
|
||||
cfg.serialize(&mut serializer)?;
|
||||
file.write_all(b"\n")?;
|
||||
file.sync_all()?;
|
||||
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
/// Load a configuration from the given path.
|
||||
pub fn load(path: &Path) -> Result<Self, ConfigError> {
|
||||
match fs::File::open(path) {
|
||||
Ok(cfg) => {
|
||||
serde_json::from_reader(cfg).map_err(|e| ConfigError::Json(path.to_path_buf(), e))
|
||||
}
|
||||
Err(e) => {
|
||||
let Ok(user) = env::var("USER") else {
|
||||
return Err(ConfigError::Io(path.to_owned(), e));
|
||||
};
|
||||
let Ok(alias) = Alias::from_str(&user) else {
|
||||
return Err(ConfigError::Io(path.to_owned(), e));
|
||||
};
|
||||
Ok(Config {
|
||||
node: node::Config::new(alias),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the user alias.
|
||||
pub fn alias(&self) -> &Alias {
|
||||
&self.node.alias
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Profile {
|
||||
pub home: Home,
|
||||
pub storage: Storage,
|
||||
pub keystore: Keystore,
|
||||
pub public_key: PublicKey,
|
||||
pub config: Config,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn init(home: Home, passphrase: impl Into<Passphrase>) -> Result<Self, Error> {
|
||||
pub fn init(
|
||||
home: Home,
|
||||
alias: Alias,
|
||||
passphrase: impl Into<Passphrase>,
|
||||
) -> Result<Self, Error> {
|
||||
let storage = Storage::open(home.storage())?;
|
||||
let keystore = Keystore::new(&home.keys());
|
||||
let public_key = keystore.init("radicle", passphrase)?;
|
||||
let config = Config::init(alias, home.config().as_path())?;
|
||||
|
||||
transport::local::register(storage.clone());
|
||||
|
||||
|
|
@ -85,6 +156,7 @@ impl Profile {
|
|||
storage,
|
||||
keystore,
|
||||
public_key,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +167,7 @@ impl Profile {
|
|||
let public_key = keystore
|
||||
.public_key()?
|
||||
.ok_or_else(|| Error::NotFound(home.path().to_path_buf()))?;
|
||||
let config = Config::load(home.config().as_path())?;
|
||||
|
||||
transport::local::register(storage.clone());
|
||||
|
||||
|
|
@ -103,6 +176,7 @@ impl Profile {
|
|||
storage,
|
||||
keystore,
|
||||
public_key,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +268,7 @@ pub struct Aliases {
|
|||
impl AliasStore for Aliases {
|
||||
/// Retrieve `alias` of given node.
|
||||
/// First looks in `tracking.db` and then `addresses.db`.
|
||||
fn alias(&self, nid: &NodeId) -> Option<String> {
|
||||
fn alias(&self, nid: &NodeId) -> Option<Alias> {
|
||||
self.tracking
|
||||
.alias(nid)
|
||||
.or_else(|| self.addresses.alias(nid))
|
||||
|
|
@ -267,6 +341,10 @@ impl Home {
|
|||
self.path.join("storage")
|
||||
}
|
||||
|
||||
pub fn config(&self) -> PathBuf {
|
||||
self.path.join("config.json")
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> PathBuf {
|
||||
self.path.join("keys")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,28 +24,53 @@ pub mod string {
|
|||
}
|
||||
}
|
||||
|
||||
/// Unlike the default `serde` instance for `LocalTime`, this encodes and decodes using seconds
|
||||
/// Unlike the default `serde` instances from `localtime`, this encodes and decodes using seconds
|
||||
/// instead of milliseconds.
|
||||
pub mod localtime {
|
||||
use localtime::LocalTime;
|
||||
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||
pub mod time {
|
||||
use localtime::LocalTime;
|
||||
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S>(value: &LocalTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&value.as_secs())
|
||||
pub fn serialize<S>(value: &LocalTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&value.as_secs())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let seconds: u64 = String::deserialize(deserializer)?
|
||||
.parse()
|
||||
.map_err(de::Error::custom)?;
|
||||
|
||||
Ok(LocalTime::from_secs(seconds))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let seconds: u64 = String::deserialize(deserializer)?
|
||||
.parse()
|
||||
.map_err(de::Error::custom)?;
|
||||
pub mod duration {
|
||||
use localtime::LocalDuration;
|
||||
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||
|
||||
Ok(LocalTime::from_secs(seconds))
|
||||
pub fn serialize<S>(value: &LocalDuration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(&value.as_secs())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalDuration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let seconds: u64 = String::deserialize(deserializer)?
|
||||
.parse()
|
||||
.map_err(de::Error::custom)?;
|
||||
|
||||
Ok(LocalDuration::from_secs(seconds))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
use std::ops::RangeBounds;
|
||||
use std::str::FromStr;
|
||||
use std::{iter, net};
|
||||
|
||||
use crypto::test::signer::MockSigner;
|
||||
|
|
@ -14,7 +15,7 @@ use crate::identity::{
|
|||
project::Project,
|
||||
Did,
|
||||
};
|
||||
use crate::node::Address;
|
||||
use crate::node::{Address, Alias};
|
||||
use crate::storage;
|
||||
use crate::storage::refs::{Refs, SignedRefs};
|
||||
use crate::test::storage::{MockRepository, MockStorage};
|
||||
|
|
@ -227,3 +228,13 @@ impl Arbitrary for Address {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Alias {
|
||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||
let s = g
|
||||
.choose(&["cloudhead", "alice", "bob", "john-lu", "f0_"])
|
||||
.unwrap();
|
||||
|
||||
Alias::from_str(s).unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue