Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ldk-server-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ edition = "2021"
[dependencies]
ldk-server-client = { path = "../ldk-server-client", features = ["serde"] }
clap = { version = "4.0.5", default-features = false, features = ["derive", "std", "error-context", "suggestions", "help"] }
hex-conservative = { version = "0.2", default-features = false, features = ["std"] }
tokio = { version = "1.38.0", default-features = false, features = ["rt-multi-thread", "macros"] }
serde = "1.0"
serde_json = "1.0"
toml = { version = "0.8", default-features = false, features = ["parse"] }
93 changes: 93 additions & 0 deletions ldk-server-cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// This file is Copyright its original authors, visible in version control
// history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.

use serde::{Deserialize, Serialize};
use std::path::PathBuf;

const DEFAULT_CONFIG_FILE: &str = "config.toml";
const DEFAULT_CERT_FILE: &str = "tls.crt";
const API_KEY_FILE: &str = "api_key";

pub fn get_default_data_dir() -> Option<PathBuf> {
#[cfg(target_os = "macos")]
{
#[allow(deprecated)]
std::env::home_dir().map(|home| home.join("Library/Application Support/ldk-server"))
}
#[cfg(target_os = "windows")]
{
std::env::var("APPDATA").ok().map(|appdata| PathBuf::from(appdata).join("ldk-server"))
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
#[allow(deprecated)]
std::env::home_dir().map(|home| home.join(".ldk-server"))
}
}

pub fn get_default_config_path() -> Option<PathBuf> {
get_default_data_dir().map(|dir| dir.join(DEFAULT_CONFIG_FILE))
}

pub fn get_default_cert_path(network: &str) -> Option<PathBuf> {
get_default_data_dir().map(|path| {
if network == "bitcoin" {
path.join(DEFAULT_CERT_FILE)
} else {
path.join(network).join(DEFAULT_CERT_FILE)
}
})
}

pub fn get_default_api_key_path(network: &str) -> Option<PathBuf> {
get_default_data_dir().map(|path| {
if network == "bitcoin" {
path.join(API_KEY_FILE)
} else {
path.join(network).join(API_KEY_FILE)
}
})
}

#[derive(Debug, Deserialize)]
pub struct Config {
pub node: NodeConfig,
pub tls: Option<TlsConfig>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct TlsConfig {
pub cert_path: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct NodeConfig {
pub rest_service_address: String,
network: String,
}

impl Config {
pub fn network(&self) -> Result<String, String> {
match self.node.network.as_str() {
"bitcoin" | "mainnet" => Ok("bitcoin".to_string()),
"testnet" => Ok("testnet".to_string()),
"testnet4" => Ok("testnet4".to_string()),
"signet" => Ok("signet".to_string()),
"regtest" => Ok("regtest".to_string()),
other => Err(format!("Unsupported network: {other}")),
}
}
}

pub fn load_config(path: &PathBuf) -> Result<Config, String> {
let contents = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read config file '{}': {}", path.display(), e))?;
toml::from_str(&contents)
.map_err(|e| format!("Failed to parse config file '{}': {}", path.display(), e))
}
82 changes: 67 additions & 15 deletions ldk-server-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
// licenses.

use clap::{Parser, Subcommand};
use config::{
get_default_api_key_path, get_default_cert_path, get_default_config_path, load_config,
};
use hex_conservative::DisplayHex;
use ldk_server_client::client::LdkServerClient;
use ldk_server_client::error::LdkServerError;
use ldk_server_client::error::LdkServerErrorCode::{
Expand All @@ -28,8 +32,10 @@ use ldk_server_client::ldk_server_protos::types::{
RouteParametersConfig,
};
use serde::Serialize;
use std::path::PathBuf;
use types::CliListPaymentsResponse;

mod config;
mod types;

// Having these default values as constants in the Proto file and
Expand All @@ -43,19 +49,25 @@ const DEFAULT_EXPIRY_SECS: u32 = 86_400;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, default_value = "localhost:3000")]
base_url: String,
#[arg(short, long, help = "Base URL of the server. If not provided, reads from config file")]
base_url: Option<String>,

#[arg(short, long, required(true))]
api_key: String,
#[arg(
short,
long,
help = "API key for authentication. If not provided, reads from config file"
)]
api_key: Option<String>,

#[arg(
short,
long,
required(true),
help = "Path to the server's TLS certificate file (PEM format). Found at <server_storage_dir>/tls_cert.pem"
help = "Path to the server's TLS certificate file (PEM format). If not provided, uses ~/.ldk-server/[network]/tls.crt"
)]
tls_cert: String,
tls_cert: Option<String>,

#[arg(short, long, help = "Path to config file. Defaults to ~/.ldk-server/config.toml")]
config: Option<String>,

#[command(subcommand)]
command: Commands,
Expand Down Expand Up @@ -226,18 +238,58 @@ enum Commands {
async fn main() {
let cli = Cli::parse();

// Load server certificate for TLS verification
let server_cert_pem = std::fs::read(&cli.tls_cert).unwrap_or_else(|e| {
eprintln!("Failed to read server certificate file '{}': {}", cli.tls_cert, e);
std::process::exit(1);
});
let config_path = cli.config.map(PathBuf::from).or_else(get_default_config_path);
let config = config_path.as_ref().and_then(|p| load_config(p).ok());

let client =
LdkServerClient::new(cli.base_url, cli.api_key, &server_cert_pem).unwrap_or_else(|e| {
eprintln!("Failed to create client: {e}");
// Get API key from argument, then from api_key file
let api_key = cli
.api_key
.or_else(|| {
// Try to read from api_key file based on network (file contains raw bytes)
let network = config.as_ref().and_then(|c| c.network().ok()).unwrap_or("bitcoin".to_string());
get_default_api_key_path(&network)
.and_then(|path| std::fs::read(&path).ok())
.map(|bytes| bytes.to_lower_hex_string())
})
.unwrap_or_else(|| {
eprintln!("API key not provided. Use --api-key or ensure the api_key file exists at ~/.ldk-server/[network]/api_key");
std::process::exit(1);
});

// Get base URL from argument then from config file
let base_url =
cli.base_url.or_else(|| config.as_ref().map(|c| c.node.rest_service_address.clone()))
.unwrap_or_else(|| {
eprintln!("Base URL not provided. Use --base-url or ensure config file exists at ~/.ldk-server/config.toml");
std::process::exit(1);
});

// Get TLS cert path from argument, then from config file, then try default location
let tls_cert_path = cli.tls_cert.map(PathBuf::from).or_else(|| {
config
.as_ref()
.and_then(|c| c.tls.as_ref().and_then(|t| t.cert_path.as_ref().map(PathBuf::from)))
.or_else(|| {
config
.as_ref()
.and_then(|c| c.network().ok().and_then(|n| get_default_cert_path(&n)))
})
})
.unwrap_or_else(|| {
eprintln!("TLS cert path not provided. Use --tls-cert or ensure config file exists at ~/.ldk-server/config.toml");
std::process::exit(1);
});

let server_cert_pem = std::fs::read(&tls_cert_path).unwrap_or_else(|e| {
eprintln!("Failed to read server certificate file '{}': {}", tls_cert_path.display(), e);
std::process::exit(1);
});

let client = LdkServerClient::new(base_url, api_key, &server_cert_pem).unwrap_or_else(|e| {
eprintln!("Failed to create client: {e}");
std::process::exit(1);
});

match cli.command {
Commands::GetNodeInfo => {
handle_response_result::<_, GetNodeInfoResponse>(
Expand Down
1 change: 0 additions & 1 deletion ldk-server/ldk-server-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
network = "regtest" # Bitcoin network to use
listening_address = "localhost:3001" # Lightning node listening address
rest_service_address = "127.0.0.1:3002" # LDK Server REST address
api_key = "your-secret-api-key" # API key for authenticating REST requests

# Storage settings
[storage.disk]
Expand Down
Loading