Skip to content

Commit 3bcf2cc

Browse files
committed
Create default data directory at ~/.ldk-server
Previously, the daemon required a config file path argument and the CLI required explicit --api-key and --tls-cert flags. Now both default to reading from ~/.ldk-server/config.toml, so you can just run the daemon and CLI without having to specify any flags. We make sure to separate out data by network so we don't conflict anywhere and accidentally lose anything important
1 parent 1fa1a2c commit 3bcf2cc

File tree

7 files changed

+291
-59
lines changed

7 files changed

+291
-59
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ldk-server-cli/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ edition = "2021"
66
[dependencies]
77
ldk-server-client = { path = "../ldk-server-client", features = ["serde"] }
88
clap = { version = "4.0.5", default-features = false, features = ["derive", "std", "error-context", "suggestions", "help"] }
9+
hex-conservative = { version = "0.2", default-features = false, features = ["std"] }
910
tokio = { version = "1.38.0", default-features = false, features = ["rt-multi-thread", "macros"] }
1011
serde = "1.0"
1112
serde_json = "1.0"
13+
toml = { version = "0.8", default-features = false, features = ["parse"] }

ldk-server-cli/src/config.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
use serde::{Deserialize, Serialize};
11+
use std::path::PathBuf;
12+
13+
const DEFAULT_CONFIG_FILE: &str = "config.toml";
14+
const DEFAULT_CERT_FILE: &str = "tls.crt";
15+
const API_KEY_FILE: &str = "api_key";
16+
17+
pub fn get_default_data_dir() -> Option<PathBuf> {
18+
#[cfg(target_os = "macos")]
19+
{
20+
#[allow(deprecated)] // todo can remove once we update MSRV to 1.87+
21+
std::env::home_dir().map(|home| home.join("Library/Application Support/ldk-server"))
22+
}
23+
#[cfg(target_os = "windows")]
24+
{
25+
std::env::var("APPDATA").ok().map(|appdata| PathBuf::from(appdata).join("ldk-server"))
26+
}
27+
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
28+
{
29+
#[allow(deprecated)] // todo can remove once we update MSRV to 1.87+
30+
std::env::home_dir().map(|home| home.join(".ldk-server"))
31+
}
32+
}
33+
34+
pub fn get_default_config_path() -> Option<PathBuf> {
35+
get_default_data_dir().map(|dir| dir.join(DEFAULT_CONFIG_FILE))
36+
}
37+
38+
pub fn get_default_cert_path(network: &str) -> Option<PathBuf> {
39+
get_default_data_dir().map(|path| {
40+
if network == "bitcoin" {
41+
path.join(DEFAULT_CERT_FILE)
42+
} else {
43+
path.join(network).join(DEFAULT_CERT_FILE)
44+
}
45+
})
46+
}
47+
48+
pub fn get_default_api_key_path(network: &str) -> Option<PathBuf> {
49+
get_default_data_dir().map(|path| {
50+
if network == "bitcoin" {
51+
path.join(API_KEY_FILE)
52+
} else {
53+
path.join(network).join(API_KEY_FILE)
54+
}
55+
})
56+
}
57+
58+
#[derive(Debug, Deserialize)]
59+
pub struct Config {
60+
pub node: NodeConfig,
61+
pub tls: Option<TlsConfig>,
62+
}
63+
64+
#[derive(Debug, Deserialize, Serialize)]
65+
pub struct TlsConfig {
66+
pub cert_path: Option<String>,
67+
}
68+
69+
#[derive(Debug, Deserialize)]
70+
pub struct NodeConfig {
71+
pub rest_service_address: String,
72+
network: String,
73+
}
74+
75+
impl Config {
76+
pub fn network(&self) -> Result<String, String> {
77+
match self.node.network.as_str() {
78+
"bitcoin" | "mainnet" => Ok("bitcoin".to_string()),
79+
"testnet" => Ok("testnet".to_string()),
80+
"testnet4" => Ok("testnet4".to_string()),
81+
"signet" => Ok("signet".to_string()),
82+
"regtest" => Ok("regtest".to_string()),
83+
other => Err(format!("Unsupported network: {other}")),
84+
}
85+
}
86+
}
87+
88+
pub fn load_config(path: &PathBuf) -> Result<Config, String> {
89+
let contents = std::fs::read_to_string(path)
90+
.map_err(|e| format!("Failed to read config file '{}': {}", path.display(), e))?;
91+
toml::from_str(&contents)
92+
.map_err(|e| format!("Failed to parse config file '{}': {}", path.display(), e))
93+
}

ldk-server-cli/src/main.rs

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
// licenses.
99

1010
use clap::{Parser, Subcommand};
11+
use config::{
12+
get_default_api_key_path, get_default_cert_path, get_default_config_path, load_config,
13+
};
14+
use hex_conservative::DisplayHex;
1115
use ldk_server_client::client::LdkServerClient;
1216
use ldk_server_client::error::LdkServerError;
1317
use ldk_server_client::error::LdkServerErrorCode::{
@@ -28,8 +32,10 @@ use ldk_server_client::ldk_server_protos::types::{
2832
RouteParametersConfig,
2933
};
3034
use serde::Serialize;
35+
use std::path::PathBuf;
3136
use types::CliListPaymentsResponse;
3237

38+
mod config;
3339
mod types;
3440

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

49-
#[arg(short, long, required(true))]
50-
api_key: String,
55+
#[arg(
56+
short,
57+
long,
58+
help = "API key for authentication. If not provided, reads from config file"
59+
)]
60+
api_key: Option<String>,
5161

5262
#[arg(
5363
short,
5464
long,
55-
required(true),
56-
help = "Path to the server's TLS certificate file (PEM format). Found at <server_storage_dir>/tls_cert.pem"
65+
help = "Path to the server's TLS certificate file (PEM format). If not provided, uses ~/.ldk-server/[network]/tls.crt"
5766
)]
58-
tls_cert: String,
67+
tls_cert: Option<String>,
68+
69+
#[arg(short, long, help = "Path to config file. Defaults to ~/.ldk-server/config.toml")]
70+
config: Option<String>,
5971

6072
#[command(subcommand)]
6173
command: Commands,
@@ -226,18 +238,58 @@ enum Commands {
226238
async fn main() {
227239
let cli = Cli::parse();
228240

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

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

259+
// Get base URL from argument then from config file
260+
let base_url =
261+
cli.base_url.or_else(|| config.as_ref().map(|c| c.node.rest_service_address.clone()))
262+
.unwrap_or_else(|| {
263+
eprintln!("Base URL not provided. Use --base-url or ensure config file exists at ~/.ldk-server/config.toml");
264+
std::process::exit(1);
265+
});
266+
267+
// Get TLS cert path from argument, then from config file, then try default location
268+
let tls_cert_path = cli.tls_cert.map(PathBuf::from).or_else(|| {
269+
config
270+
.as_ref()
271+
.and_then(|c| c.tls.as_ref().and_then(|t| t.cert_path.as_ref().map(PathBuf::from)))
272+
.or_else(|| {
273+
config
274+
.as_ref()
275+
.and_then(|c| c.network().ok().and_then(|n| get_default_cert_path(&n)))
276+
})
277+
})
278+
.unwrap_or_else(|| {
279+
eprintln!("TLS cert path not provided. Use --tls-cert or ensure config file exists at ~/.ldk-server/config.toml");
280+
std::process::exit(1);
281+
});
282+
283+
let server_cert_pem = std::fs::read(&tls_cert_path).unwrap_or_else(|e| {
284+
eprintln!("Failed to read server certificate file '{}': {}", tls_cert_path.display(), e);
285+
std::process::exit(1);
286+
});
287+
288+
let client = LdkServerClient::new(base_url, api_key, &server_cert_pem).unwrap_or_else(|e| {
289+
eprintln!("Failed to create client: {e}");
290+
std::process::exit(1);
291+
});
292+
241293
match cli.command {
242294
Commands::GetNodeInfo => {
243295
handle_response_result::<_, GetNodeInfoResponse>(

ldk-server/ldk-server-config.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
network = "regtest" # Bitcoin network to use
44
listening_address = "localhost:3001" # Lightning node listening address
55
rest_service_address = "127.0.0.1:3002" # LDK Server REST address
6-
api_key = "your-secret-api-key" # API key for authenticating REST requests
76

87
# Storage settings
98
[storage.disk]

0 commit comments

Comments
 (0)