Tidy up environment variables
* Try to use constants instead of strings * Move crypto seed override out of radicle-crypto * Use GIT_COMMITTER_DATE for commit overrides * Use RAD_LOCAL_TIME as general time override * Allow variables to be used in release mode
This commit is contained in:
parent
deeb39c558
commit
fe5757d122
|
|
@ -1,5 +1,4 @@
|
||||||
#![allow(clippy::or_fun_call)]
|
#![allow(clippy::or_fun_call)]
|
||||||
use std::env;
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::ops::Not as _;
|
use std::ops::Not as _;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
@ -9,7 +8,7 @@ use anyhow::anyhow;
|
||||||
use radicle::crypto::ssh;
|
use radicle::crypto::ssh;
|
||||||
use radicle::crypto::ssh::Passphrase;
|
use radicle::crypto::ssh::Passphrase;
|
||||||
use radicle::node::Alias;
|
use radicle::node::Alias;
|
||||||
use radicle::profile::env::RAD_PASSPHRASE;
|
use radicle::profile::env;
|
||||||
use radicle::{profile, Profile};
|
use radicle::{profile, Profile};
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
@ -108,11 +107,11 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
let passphrase = if options.stdin {
|
let passphrase = if options.stdin {
|
||||||
term::passphrase_stdin()
|
term::passphrase_stdin()
|
||||||
} else {
|
} else {
|
||||||
term::passphrase_confirm("Enter a passphrase:", RAD_PASSPHRASE)
|
term::passphrase_confirm("Enter a passphrase:", env::RAD_PASSPHRASE)
|
||||||
}?;
|
}?;
|
||||||
let passphrase = passphrase.trim().is_empty().not().then_some(passphrase);
|
let passphrase = passphrase.trim().is_empty().not().then_some(passphrase);
|
||||||
let spinner = term::spinner("Creating your Ed25519 keypair...");
|
let spinner = term::spinner("Creating your Ed25519 keypair...");
|
||||||
let profile = Profile::init(home, alias, passphrase.clone())?;
|
let profile = Profile::init(home, alias, passphrase.clone(), env::seed())?;
|
||||||
let mut agent = true;
|
let mut agent = true;
|
||||||
spinner.finish();
|
spinner.finish();
|
||||||
|
|
||||||
|
|
@ -199,7 +198,7 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
||||||
// Try RAD_PASSPHRASE fallback.
|
// Try RAD_PASSPHRASE fallback.
|
||||||
if let Some(passphrase) = profile::env::passphrase() {
|
if let Some(passphrase) = profile::env::passphrase() {
|
||||||
ssh::keystore::MemorySigner::load(&profile.keystore, Some(passphrase))
|
ssh::keystore::MemorySigner::load(&profile.keystore, Some(passphrase))
|
||||||
.map_err(|_| anyhow!("`{RAD_PASSPHRASE}` is invalid"))?;
|
.map_err(|_| anyhow!("`{}` is invalid", env::RAD_PASSPHRASE))?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ use localtime::LocalTime;
|
||||||
pub use radicle_term::format::*;
|
pub use radicle_term::format::*;
|
||||||
pub use radicle_term::{style, Paint};
|
pub use radicle_term::{style, Paint};
|
||||||
|
|
||||||
use radicle::cob::{ObjectId, Timestamp};
|
use radicle::cob::ObjectId;
|
||||||
use radicle::identity::Visibility;
|
use radicle::identity::Visibility;
|
||||||
use radicle::node::policy::Policy;
|
use radicle::node::policy::Policy;
|
||||||
use radicle::node::{Alias, AliasStore, NodeId};
|
use radicle::node::{Alias, AliasStore, NodeId};
|
||||||
use radicle::prelude::Did;
|
use radicle::prelude::Did;
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::{env, Profile};
|
||||||
use radicle::storage::RefUpdate;
|
use radicle::storage::RefUpdate;
|
||||||
use radicle_term::element::Line;
|
use radicle_term::element::Line;
|
||||||
|
|
||||||
|
|
@ -74,8 +74,8 @@ pub fn policy(p: &Policy) -> Paint<String> {
|
||||||
|
|
||||||
/// Format a timestamp.
|
/// Format a timestamp.
|
||||||
pub fn timestamp(time: impl Into<LocalTime>) -> Paint<String> {
|
pub fn timestamp(time: impl Into<LocalTime>) -> Paint<String> {
|
||||||
let time: LocalTime = time.into();
|
let time = time.into();
|
||||||
let now: LocalTime = Timestamp::now().into();
|
let now = env::local_time();
|
||||||
let duration = now - time;
|
let duration = now - time;
|
||||||
let fmt = timeago::Formatter::new();
|
let fmt = timeago::Formatter::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{env, net, thread, time};
|
use std::{net, thread, time};
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::node;
|
use radicle::node;
|
||||||
|
|
@ -11,7 +11,7 @@ use radicle::node::Handle as _;
|
||||||
use radicle::node::{Address, Alias, DEFAULT_TIMEOUT};
|
use radicle::node::{Address, Alias, DEFAULT_TIMEOUT};
|
||||||
use radicle::prelude::{NodeId, RepoId};
|
use radicle::prelude::{NodeId, RepoId};
|
||||||
use radicle::profile;
|
use radicle::profile;
|
||||||
use radicle::profile::Home;
|
use radicle::profile::{env, Home};
|
||||||
use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository};
|
use radicle::storage::{ReadStorage, RefUpdate, RemoteRepository};
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
|
|
||||||
|
|
@ -105,14 +105,14 @@ fn formula(root: &Path, test: impl AsRef<Path>) -> Result<TestFormula, Box<dyn s
|
||||||
.env("GIT_COMMITTER_DATE", "1671125284")
|
.env("GIT_COMMITTER_DATE", "1671125284")
|
||||||
.env("GIT_COMMITTER_EMAIL", "radicle@localhost")
|
.env("GIT_COMMITTER_EMAIL", "radicle@localhost")
|
||||||
.env("GIT_COMMITTER_NAME", "radicle")
|
.env("GIT_COMMITTER_NAME", "radicle")
|
||||||
.env("RAD_PASSPHRASE", "radicle")
|
|
||||||
.env("RAD_SEED", RAD_SEED)
|
|
||||||
.env("RAD_RNG_SEED", "0")
|
|
||||||
.env("EDITOR", "true")
|
.env("EDITOR", "true")
|
||||||
.env("TZ", "UTC")
|
.env("TZ", "UTC")
|
||||||
.env("LANG", "C")
|
.env("LANG", "C")
|
||||||
.env("USER", "alice")
|
.env("USER", "alice")
|
||||||
.env(radicle_cob::git::RAD_COMMIT_TIME, "1671125284")
|
.env(env::RAD_PASSPHRASE, "radicle")
|
||||||
|
.env(env::RAD_SEED, RAD_SEED)
|
||||||
|
.env(env::RAD_RNG_SEED, "0")
|
||||||
|
.env(env::RAD_LOCAL_TIME, "1671125284")
|
||||||
.envs(git::env::GIT_DEFAULT_CONFIG)
|
.envs(git::env::GIT_DEFAULT_CONFIG)
|
||||||
.build(&[
|
.build(&[
|
||||||
("radicle-remote-helper", "git-remote-rad"),
|
("radicle-remote-helper", "git-remote-rad"),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright © 2022 The Radicle Link Contributors
|
// Copyright © 2022 The Radicle Team
|
||||||
|
|
||||||
pub mod change;
|
pub mod change;
|
||||||
|
|
||||||
/// Environment variable to set to overwrite the commit date for both the author and the committer.
|
/// Environment variable to set to overwrite the commit date for both the author and the committer.
|
||||||
///
|
///
|
||||||
/// The format must be a unix timestamp.
|
/// The format must be a unix timestamp.
|
||||||
pub const RAD_COMMIT_TIME: &str = "RAD_COMMIT_TIME";
|
pub const GIT_COMMITTER_DATE: &str = "GIT_COMMITTER_DATE";
|
||||||
|
|
|
||||||
|
|
@ -308,11 +308,13 @@ fn write_commit(
|
||||||
},
|
},
|
||||||
1514817556,
|
1514817556,
|
||||||
);
|
);
|
||||||
#[cfg(debug_assertions)]
|
let (author, timestamp) = if let Ok(s) = std::env::var(crate::git::GIT_COMMITTER_DATE) {
|
||||||
let (author, timestamp) = if let Ok(s) = std::env::var(crate::git::RAD_COMMIT_TIME) {
|
let Ok(timestamp) = s.trim().parse::<i64>() else {
|
||||||
// SAFETY: It's ok to panic here, since this is only enabled in debug mode.
|
panic!(
|
||||||
#[allow(clippy::unwrap_used)]
|
"Invalid timestamp value {s:?} for `{}`",
|
||||||
let timestamp = s.trim().parse::<i64>().unwrap();
|
crate::git::GIT_COMMITTER_DATE
|
||||||
|
);
|
||||||
|
};
|
||||||
let author = Author {
|
let author = Author {
|
||||||
time: git_ext::author::Time::new(timestamp, 0),
|
time: git_ext::author::Time::new(timestamp, 0),
|
||||||
..author
|
..author
|
||||||
|
|
|
||||||
|
|
@ -485,43 +485,6 @@ impl sqlite::BindableWithIndex for &Signature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod keypair {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Generate a new keypair using OS randomness.
|
|
||||||
pub fn generate() -> KeyPair {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if let Some(seed) = env::seed() {
|
|
||||||
// Generate a keypair based on the given environment variable.
|
|
||||||
// This is useful for debugging and testing, since the
|
|
||||||
// public key can be known in advance.
|
|
||||||
return KeyPair::from_seed(Seed::new(seed));
|
|
||||||
}
|
|
||||||
KeyPair::generate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod env {
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
/// Return the seed stored in the `RAD_SEED` environment variable, if any.
|
|
||||||
pub fn seed() -> Option<[u8; 32]> {
|
|
||||||
if let Ok(seed) = env::var("RAD_SEED") {
|
|
||||||
let seed = (0..seed.len())
|
|
||||||
.step_by(2)
|
|
||||||
.map(|i| u8::from_str_radix(&seed[i..i + 2], 16))
|
|
||||||
.collect::<Result<Vec<u8>, _>>()
|
|
||||||
.expect("env::seed: invalid hexadecimal value set in `RAD_SEED`");
|
|
||||||
let seed: [u8; 32] = seed
|
|
||||||
.try_into()
|
|
||||||
.expect("env::seed: invalid seed length set in `RAD_SEED`");
|
|
||||||
|
|
||||||
return Some(seed);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::KeyPair;
|
use super::KeyPair;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use cyphernet::{EcSk, EcSkInvalid, Ecdh};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
use crate::{keypair, KeyPair, PublicKey, SecretKey, Signature, Signer, SignerError};
|
use crate::{KeyPair, PublicKey, SecretKey, Signature, Signer, SignerError};
|
||||||
|
|
||||||
/// A secret key passphrase.
|
/// A secret key passphrase.
|
||||||
pub type Passphrase = Zeroizing<String>;
|
pub type Passphrase = Zeroizing<String>;
|
||||||
|
|
@ -58,10 +58,16 @@ impl Keystore {
|
||||||
///
|
///
|
||||||
/// The `comment` is associated with the private key.
|
/// The `comment` is associated with the private key.
|
||||||
/// The `passphrase` is used to encrypt the private key.
|
/// The `passphrase` is used to encrypt the private key.
|
||||||
|
/// The `seed` is used to derive the private key and should almost always be generated.
|
||||||
///
|
///
|
||||||
/// If `passphrase` is `None`, the key is not encrypted.
|
/// If `passphrase` is `None`, the key is not encrypted.
|
||||||
pub fn init(&self, comment: &str, passphrase: Option<Passphrase>) -> Result<PublicKey, Error> {
|
pub fn init(
|
||||||
self.store(keypair::generate(), comment, passphrase)
|
&self,
|
||||||
|
comment: &str,
|
||||||
|
passphrase: Option<Passphrase>,
|
||||||
|
seed: ec25519::Seed,
|
||||||
|
) -> Result<PublicKey, Error> {
|
||||||
|
self.store(KeyPair::from_seed(seed), comment, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store a keypair on disk. Returns an error if the key already exists.
|
/// Store a keypair on disk. Returns an error if the key already exists.
|
||||||
|
|
@ -285,7 +291,11 @@ mod tests {
|
||||||
let store = Keystore::new(&tmp.path());
|
let store = Keystore::new(&tmp.path());
|
||||||
|
|
||||||
let public = store
|
let public = store
|
||||||
.init("test", Some("hunter".to_owned().into()))
|
.init(
|
||||||
|
"test",
|
||||||
|
Some("hunter".to_owned().into()),
|
||||||
|
ec25519::Seed::default(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(public, store.public_key().unwrap().unwrap());
|
assert_eq!(public, store.public_key().unwrap().unwrap());
|
||||||
assert!(store.is_encrypted().unwrap());
|
assert!(store.is_encrypted().unwrap());
|
||||||
|
|
@ -306,7 +316,7 @@ mod tests {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
let store = Keystore::new(&tmp.path());
|
let store = Keystore::new(&tmp.path());
|
||||||
|
|
||||||
let public = store.init("test", None).unwrap();
|
let public = store.init("test", None, ec25519::Seed::default()).unwrap();
|
||||||
assert_eq!(public, store.public_key().unwrap().unwrap());
|
assert_eq!(public, store.public_key().unwrap().unwrap());
|
||||||
assert!(!store.is_encrypted().unwrap());
|
assert!(!store.is_encrypted().unwrap());
|
||||||
|
|
||||||
|
|
@ -320,7 +330,11 @@ mod tests {
|
||||||
let store = Keystore::new(&tmp.path());
|
let store = Keystore::new(&tmp.path());
|
||||||
|
|
||||||
let public = store
|
let public = store
|
||||||
.init("test", Some("hunter".to_owned().into()))
|
.init(
|
||||||
|
"test",
|
||||||
|
Some("hunter".to_owned().into()),
|
||||||
|
ec25519::Seed::default(),
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let signer = MemorySigner::load(&store, Some("hunter".to_owned().into())).unwrap();
|
let signer = MemorySigner::load(&store, Some("hunter".to_owned().into())).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{env, fs};
|
|
||||||
|
|
||||||
use axum::body::{Body, Bytes};
|
use axum::body::{Body, Bytes};
|
||||||
use axum::http::{Method, Request};
|
use axum::http::{Method, Request};
|
||||||
|
|
@ -17,7 +17,7 @@ use radicle::crypto::ssh::Keystore;
|
||||||
use radicle::crypto::{KeyPair, Seed, Signer};
|
use radicle::crypto::{KeyPair, Seed, Signer};
|
||||||
use radicle::git::{raw as git2, RefString};
|
use radicle::git::{raw as git2, RefString};
|
||||||
use radicle::identity::Visibility;
|
use radicle::identity::Visibility;
|
||||||
use radicle::profile::Home;
|
use radicle::profile::{env, Home};
|
||||||
use radicle::storage::ReadStorage;
|
use radicle::storage::ReadStorage;
|
||||||
use radicle::Storage;
|
use radicle::Storage;
|
||||||
use radicle::{node, profile};
|
use radicle::{node, profile};
|
||||||
|
|
@ -144,7 +144,7 @@ fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G
|
||||||
|
|
||||||
let workdir = dir.join("hello-world");
|
let workdir = dir.join("hello-world");
|
||||||
|
|
||||||
env::set_var("RAD_COMMIT_TIME", TIMESTAMP.to_string());
|
env::set_var(env::GIT_COMMITTER_DATE, TIMESTAMP.to_string());
|
||||||
|
|
||||||
fs::create_dir_all(&workdir).unwrap();
|
fs::create_dir_all(&workdir).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::mem::ManuallyDrop;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
env, fs, io, iter, net, process, thread, time,
|
fs, io, iter, net, process, thread, time,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ use radicle::node::Database;
|
||||||
use radicle::node::{Alias, POLICIES_DB_FILE};
|
use radicle::node::{Alias, POLICIES_DB_FILE};
|
||||||
use radicle::node::{ConnectOptions, Handle as _};
|
use radicle::node::{ConnectOptions, Handle as _};
|
||||||
use radicle::profile;
|
use radicle::profile;
|
||||||
use radicle::profile::{Home, Profile};
|
use radicle::profile::{env, Home, Profile};
|
||||||
use radicle::rad;
|
use radicle::rad;
|
||||||
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
|
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
|
|
@ -413,11 +413,14 @@ impl<G: Signer + cyphernet::Ecdh> NodeHandle<G> {
|
||||||
.env("GIT_COMMITTER_DATE", "1671125284")
|
.env("GIT_COMMITTER_DATE", "1671125284")
|
||||||
.env("GIT_COMMITTER_EMAIL", "radicle@localhost")
|
.env("GIT_COMMITTER_EMAIL", "radicle@localhost")
|
||||||
.env("GIT_COMMITTER_NAME", "radicle")
|
.env("GIT_COMMITTER_NAME", "radicle")
|
||||||
.env("RAD_HOME", self.home.path().to_string_lossy().to_string())
|
.env(
|
||||||
.env("RAD_PASSPHRASE", "radicle")
|
env::RAD_HOME,
|
||||||
|
self.home.path().to_string_lossy().to_string(),
|
||||||
|
)
|
||||||
|
.env(env::RAD_PASSPHRASE, "radicle")
|
||||||
|
.env(env::RAD_LOCAL_TIME, "1671125284")
|
||||||
.env("TZ", "UTC")
|
.env("TZ", "UTC")
|
||||||
.env("LANG", "C")
|
.env("LANG", "C")
|
||||||
.env(radicle::cob::git::RAD_COMMIT_TIME, "1671125284")
|
|
||||||
.envs(git::env::GIT_DEFAULT_CONFIG)
|
.envs(git::env::GIT_DEFAULT_CONFIG)
|
||||||
.current_dir(cwd)
|
.current_dir(cwd)
|
||||||
.arg(cmd)
|
.arg(cmd)
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.map_err(|_| Error::NoGitDir)?;
|
.map_err(|_| Error::NoGitDir)?;
|
||||||
// Whether we should output debug logs.
|
// Whether we should output debug logs.
|
||||||
let debug = env::var("RAD_DEBUG").is_ok();
|
let debug = radicle::profile::env::debug();
|
||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
|
|
|
||||||
|
|
@ -19,27 +19,6 @@ use crate::storage::ReadRepository;
|
||||||
pub struct Timestamp(LocalTime);
|
pub struct Timestamp(LocalTime);
|
||||||
|
|
||||||
impl Timestamp {
|
impl Timestamp {
|
||||||
/// Construct a `Timestamp` corresponding to the current time.
|
|
||||||
///
|
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// If this is used in debug mode, `RAD_COMMIT_TIME` will be used
|
|
||||||
/// to construct the timestamp.
|
|
||||||
pub fn now() -> Self {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
if let Ok(s) = std::env::var("RAD_COMMIT_TIME") {
|
|
||||||
// SAFETY: Only used in test code.
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
let secs = s.trim().parse::<u64>().unwrap();
|
|
||||||
Self::from_secs(secs)
|
|
||||||
} else {
|
|
||||||
Self(LocalTime::now())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self(LocalTime::now())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_secs(secs: u64) -> Self {
|
pub fn from_secs(secs: u64) -> Self {
|
||||||
Self(LocalTime::from_secs(secs))
|
Self(LocalTime::from_secs(secs))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2686,6 +2686,7 @@ mod test {
|
||||||
use crate::crypto::test::signer::MockSigner;
|
use crate::crypto::test::signer::MockSigner;
|
||||||
use crate::identity;
|
use crate::identity;
|
||||||
use crate::patch::cache::Patches as _;
|
use crate::patch::cache::Patches as _;
|
||||||
|
use crate::profile::env;
|
||||||
use crate::test;
|
use crate::test;
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
use crate::test::arbitrary::gen;
|
use crate::test::arbitrary::gen;
|
||||||
|
|
@ -2996,7 +2997,7 @@ mod test {
|
||||||
let base = arbitrary::oid();
|
let base = arbitrary::oid();
|
||||||
let oid = arbitrary::oid();
|
let oid = arbitrary::oid();
|
||||||
let repo = gen::<MockRepository>(1);
|
let repo = gen::<MockRepository>(1);
|
||||||
let time = Timestamp::now();
|
let time = env::local_time();
|
||||||
let alice = MockSigner::default();
|
let alice = MockSigner::default();
|
||||||
let bob = MockSigner::default();
|
let bob = MockSigner::default();
|
||||||
let mut h0: cob::test::HistoryBuilder<Patch> = cob::test::history(
|
let mut h0: cob::test::HistoryBuilder<Patch> = cob::test::history(
|
||||||
|
|
@ -3012,7 +3013,7 @@ mod test {
|
||||||
target: MergeTarget::Delegates,
|
target: MergeTarget::Delegates,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
time,
|
time.into(),
|
||||||
&alice,
|
&alice,
|
||||||
);
|
);
|
||||||
let r1 = h0.commit(
|
let r1 = h0.commit(
|
||||||
|
|
|
||||||
|
|
@ -689,11 +689,12 @@ mod tests {
|
||||||
|
|
||||||
use crate::cob::cache::{Store, Update, Write};
|
use crate::cob::cache::{Store, Update, Write};
|
||||||
use crate::cob::thread::{Comment, Thread};
|
use crate::cob::thread::{Comment, Thread};
|
||||||
use crate::cob::{Author, Timestamp};
|
use crate::cob::Author;
|
||||||
use crate::patch::{
|
use crate::patch::{
|
||||||
ByRevision, MergeTarget, Patch, PatchCounts, PatchId, Revision, RevisionId, State, Status,
|
ByRevision, MergeTarget, Patch, PatchCounts, PatchId, Revision, RevisionId, State, Status,
|
||||||
};
|
};
|
||||||
use crate::prelude::Did;
|
use crate::prelude::Did;
|
||||||
|
use crate::profile::env;
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
use crate::test::storage::MockRepository;
|
use crate::test::storage::MockRepository;
|
||||||
|
|
||||||
|
|
@ -709,14 +710,14 @@ mod tests {
|
||||||
let description = arbitrary::gen::<String>(1);
|
let description = arbitrary::gen::<String>(1);
|
||||||
let base = arbitrary::oid();
|
let base = arbitrary::oid();
|
||||||
let oid = arbitrary::oid();
|
let oid = arbitrary::oid();
|
||||||
let timestamp = Timestamp::now();
|
let timestamp = env::local_time();
|
||||||
let resolves = BTreeSet::new();
|
let resolves = BTreeSet::new();
|
||||||
let mut revision = Revision::new(
|
let mut revision = Revision::new(
|
||||||
Author { id: author },
|
Author { id: author },
|
||||||
description,
|
description,
|
||||||
base,
|
base,
|
||||||
oid,
|
oid,
|
||||||
timestamp,
|
timestamp.into(),
|
||||||
resolves,
|
resolves,
|
||||||
);
|
);
|
||||||
let comment = Comment::new(
|
let comment = Comment::new(
|
||||||
|
|
@ -725,7 +726,7 @@ mod tests {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
vec![],
|
vec![],
|
||||||
Timestamp::now(),
|
timestamp.into(),
|
||||||
);
|
);
|
||||||
let thread = Thread::new(arbitrary::oid(), comment);
|
let thread = Thread::new(arbitrary::oid(), comment);
|
||||||
revision.discussion = thread;
|
revision.discussion = thread;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use crate::git::ext::commit::headers::Headers;
|
||||||
use crate::git::ext::commit::{trailers::OwnedTrailer, Commit};
|
use crate::git::ext::commit::{trailers::OwnedTrailer, Commit};
|
||||||
use crate::git::Oid;
|
use crate::git::Oid;
|
||||||
use crate::prelude::Did;
|
use crate::prelude::Did;
|
||||||
|
use crate::profile::env;
|
||||||
use crate::storage::ReadRepository;
|
use crate::storage::ReadRepository;
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
|
|
||||||
|
|
@ -203,9 +204,9 @@ impl<G: Signer> Actor<G> {
|
||||||
T::Action: Clone + Serialize,
|
T::Action: Clone + Serialize,
|
||||||
{
|
{
|
||||||
let identity = arbitrary::oid();
|
let identity = arbitrary::oid();
|
||||||
let timestamp = Timestamp::now();
|
let timestamp = env::commit_time();
|
||||||
|
|
||||||
self.op_with::<T>(actions, Some(identity), timestamp)
|
self.op_with::<T>(actions, Some(identity), timestamp.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the actor's DID.
|
/// Get the actor's DID.
|
||||||
|
|
|
||||||
|
|
@ -632,6 +632,7 @@ mod tests {
|
||||||
use crate::cob::test;
|
use crate::cob::test;
|
||||||
use crate::crypto::test::signer::MockSigner;
|
use crate::crypto::test::signer::MockSigner;
|
||||||
use crate::crypto::Signer;
|
use crate::crypto::Signer;
|
||||||
|
use crate::profile::env;
|
||||||
use crate::test::arbitrary;
|
use crate::test::arbitrary;
|
||||||
use crate::test::arbitrary::gen;
|
use crate::test::arbitrary::gen;
|
||||||
use crate::test::storage::MockRepository;
|
use crate::test::storage::MockRepository;
|
||||||
|
|
@ -743,14 +744,14 @@ mod tests {
|
||||||
let bob = MockSigner::default();
|
let bob = MockSigner::default();
|
||||||
let eve = MockSigner::default();
|
let eve = MockSigner::default();
|
||||||
let repo = gen::<MockRepository>(1);
|
let repo = gen::<MockRepository>(1);
|
||||||
let time = Timestamp::now();
|
let time = env::local_time();
|
||||||
|
|
||||||
let mut a = test::history::<Thread, _>(
|
let mut a = test::history::<Thread, _>(
|
||||||
&[Action::Comment {
|
&[Action::Comment {
|
||||||
body: "Thread root".to_owned(),
|
body: "Thread root".to_owned(),
|
||||||
reply_to: None,
|
reply_to: None,
|
||||||
}],
|
}],
|
||||||
time,
|
time.into(),
|
||||||
&alice,
|
&alice,
|
||||||
);
|
);
|
||||||
a.comment("Alice comment", Some(*a.root().id()), &alice);
|
a.comment("Alice comment", Some(*a.root().id()), &alice);
|
||||||
|
|
@ -810,14 +811,14 @@ mod tests {
|
||||||
let repo = gen::<MockRepository>(1);
|
let repo = gen::<MockRepository>(1);
|
||||||
let alice = MockSigner::default();
|
let alice = MockSigner::default();
|
||||||
let bob = MockSigner::default();
|
let bob = MockSigner::default();
|
||||||
let time = Timestamp::now();
|
let time = env::local_time();
|
||||||
|
|
||||||
let mut a = test::history::<Thread, _>(
|
let mut a = test::history::<Thread, _>(
|
||||||
&[Action::Comment {
|
&[Action::Comment {
|
||||||
body: "Thread root".to_owned(),
|
body: "Thread root".to_owned(),
|
||||||
reply_to: None,
|
reply_to: None,
|
||||||
}],
|
}],
|
||||||
time,
|
time.into(),
|
||||||
&alice,
|
&alice,
|
||||||
);
|
);
|
||||||
let mut b = a.clone();
|
let mut b = a.clone();
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,3 @@ pub mod prelude {
|
||||||
BranchName, ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage,
|
BranchName, ReadRepository, ReadStorage, SignRepository, WriteRepository, WriteStorage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod env {
|
|
||||||
pub use crypto::env::*;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,38 @@ pub mod env {
|
||||||
pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE";
|
pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE";
|
||||||
/// RNG seed. Must be convertible to a `u64`.
|
/// RNG seed. Must be convertible to a `u64`.
|
||||||
pub const RAD_RNG_SEED: &str = "RAD_RNG_SEED";
|
pub const RAD_RNG_SEED: &str = "RAD_RNG_SEED";
|
||||||
|
/// Private key seed. Used for generating deterministic keypairs.
|
||||||
|
pub const RAD_SEED: &str = "RAD_SEED";
|
||||||
/// Show radicle hints.
|
/// Show radicle hints.
|
||||||
pub const RAD_HINT: &str = "RAD_HINT";
|
pub const RAD_HINT: &str = "RAD_HINT";
|
||||||
|
/// Environment variable to set to overwrite the commit date for both
|
||||||
|
/// the author and the committer.
|
||||||
|
///
|
||||||
|
/// The format must be a unix timestamp.
|
||||||
|
pub const RAD_COMMIT_TIME: &str = "RAD_COMMIT_TIME";
|
||||||
|
/// Override the device's local time.
|
||||||
|
/// The format must be a unix timestamp.
|
||||||
|
pub const RAD_LOCAL_TIME: &str = "RAD_LOCAL_TIME";
|
||||||
|
// Turn debug mode on.
|
||||||
|
pub const RAD_DEBUG: &str = "RAD_DEBUG";
|
||||||
|
// Used to set the Git committer timestamp. Can be overridden
|
||||||
|
// to generate deterministic COB IDs.
|
||||||
|
pub const GIT_COMMITTER_DATE: &str = "GIT_COMMITTER_DATE";
|
||||||
|
|
||||||
|
/// Commit timestamp to use. Can be overriden by [`RAD_COMMIT_TIME`].
|
||||||
|
pub fn commit_time() -> localtime::LocalTime {
|
||||||
|
time(RAD_COMMIT_TIME).unwrap_or_else(local_time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Local time. Can be overriden by [`RAD_LOCAL_TIME`].
|
||||||
|
pub fn local_time() -> localtime::LocalTime {
|
||||||
|
time(RAD_LOCAL_TIME).unwrap_or_else(localtime::LocalTime::now)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether debug mode is on.
|
||||||
|
pub fn debug() -> bool {
|
||||||
|
var(RAD_DEBUG).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether or not to show hints.
|
/// Whether or not to show hints.
|
||||||
pub fn hints() -> bool {
|
pub fn hints() -> bool {
|
||||||
|
|
@ -74,15 +104,47 @@ pub mod env {
|
||||||
/// Get a random number generator from the environment.
|
/// Get a random number generator from the environment.
|
||||||
pub fn rng() -> fastrand::Rng {
|
pub fn rng() -> fastrand::Rng {
|
||||||
if let Ok(seed) = var(RAD_RNG_SEED) {
|
if let Ok(seed) = var(RAD_RNG_SEED) {
|
||||||
return fastrand::Rng::with_seed(
|
let Ok(seed) = seed.parse() else {
|
||||||
seed.parse()
|
panic!("env::rng: invalid seed specified in `{RAD_RNG_SEED}`");
|
||||||
.expect("env::rng: invalid seed specified in `RAD_RNG_SEED`"),
|
};
|
||||||
);
|
fastrand::Rng::with_seed(seed)
|
||||||
}
|
} else {
|
||||||
fastrand::Rng::new()
|
fastrand::Rng::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the seed stored in the [`RAD_SEED`] environment variable, or generate a random one.
|
||||||
|
pub fn seed() -> crypto::Seed {
|
||||||
|
if let Ok(seed) = var(RAD_SEED) {
|
||||||
|
let Ok(seed) = (0..seed.len())
|
||||||
|
.step_by(2)
|
||||||
|
.map(|i| u8::from_str_radix(&seed[i..i + 2], 16))
|
||||||
|
.collect::<Result<Vec<u8>, _>>()
|
||||||
|
else {
|
||||||
|
panic!("env::seed: invalid hexadecimal value set in `{RAD_SEED}`");
|
||||||
|
};
|
||||||
|
let Ok(seed): Result<[u8; 32], _> = seed.try_into() else {
|
||||||
|
panic!("env::seed: invalid seed length set in `{RAD_SEED}`");
|
||||||
|
};
|
||||||
|
crypto::Seed::new(seed)
|
||||||
|
} else {
|
||||||
|
crypto::Seed::generate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time(key: &str) -> Option<localtime::LocalTime> {
|
||||||
|
if let Ok(s) = var(key) {
|
||||||
|
match s.trim().parse::<u64>() {
|
||||||
|
Ok(t) => return Some(localtime::LocalTime::from_secs(t)),
|
||||||
|
Err(e) => {
|
||||||
|
panic!("env::time: invalid value {s:?} for `{key}` environment variable: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
@ -206,9 +268,14 @@ pub struct Profile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Profile {
|
impl Profile {
|
||||||
pub fn init(home: Home, alias: Alias, passphrase: Option<Passphrase>) -> Result<Self, Error> {
|
pub fn init(
|
||||||
|
home: Home,
|
||||||
|
alias: Alias,
|
||||||
|
passphrase: Option<Passphrase>,
|
||||||
|
seed: crypto::Seed,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
let keystore = Keystore::new(&home.keys());
|
let keystore = Keystore::new(&home.keys());
|
||||||
let public_key = keystore.init("radicle", passphrase)?;
|
let public_key = keystore.init("radicle", passphrase, seed)?;
|
||||||
let config = Config::init(alias.clone(), home.config().as_path())?;
|
let config = Config::init(alias.clone(), home.config().as_path())?;
|
||||||
let storage = Storage::open(
|
let storage = Storage::open(
|
||||||
home.storage(),
|
home.storage(),
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use thiserror::Error;
|
||||||
use crate::git;
|
use crate::git;
|
||||||
use crate::git::ext as git_ext;
|
use crate::git::ext as git_ext;
|
||||||
use crate::git::Oid;
|
use crate::git::Oid;
|
||||||
|
use crate::profile::env;
|
||||||
use crate::storage;
|
use crate::storage;
|
||||||
use crate::storage::{ReadRepository, RemoteId, WriteRepository};
|
use crate::storage::{ReadRepository, RemoteId, WriteRepository};
|
||||||
|
|
||||||
|
|
@ -309,17 +310,17 @@ impl SignedRefs<Verified> {
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let sigref = sigref.with_namespace(remote.into());
|
let sigref = sigref.with_namespace(remote.into());
|
||||||
let author = raw.signature()?;
|
let author = if let Ok(s) = env::var(env::GIT_COMMITTER_DATE) {
|
||||||
|
let Ok(timestamp) = s.trim().parse::<i64>() else {
|
||||||
#[cfg(debug_assertions)]
|
panic!(
|
||||||
let author = if let Ok(s) = std::env::var("RAD_COMMIT_TIME") {
|
"Invalid timestamp value {s:?} for `{}`",
|
||||||
// SAFETY: Only used in test code.
|
env::GIT_COMMITTER_DATE
|
||||||
#[allow(clippy::unwrap_used)]
|
);
|
||||||
let timestamp = s.trim().parse::<i64>().unwrap();
|
};
|
||||||
let time = git2::Time::new(timestamp, 0);
|
let time = git2::Time::new(timestamp, 0);
|
||||||
git2::Signature::new("radicle", remote.to_string().as_str(), &time)?
|
git2::Signature::new("radicle", remote.to_string().as_str(), &time)?
|
||||||
} else {
|
} else {
|
||||||
author
|
raw.signature()?
|
||||||
};
|
};
|
||||||
|
|
||||||
let commit = raw.commit(
|
let commit = raw.commit(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue