1use std::io::Error;
2use std::path::{Path, PathBuf};
3use std::sync::{LazyLock, OnceLock};
4
5mod config;
6mod db;
7mod ssh;
8
9static WORK_DIR: OnceLock<PathBuf> = OnceLock::new();
10pub(crate) static WORK_DIR_FILE: LazyLock<fn(&str) -> PathBuf> = LazyLock::new(|| {
11 |n| {
12 if cfg!(test) {
13 let work_dir =
14 std::env::current_exe().expect("failed to get current execute directory");
15 work_dir.with_file_name("test.atsh.d")
16 } else {
17 let work_dir = WORK_DIR.get().expect("WORK_DIR not initialized");
18 work_dir.join(n)
19 }
20 }
21});
22
23fn setup_logging(work_dir: &Path) -> Result<(), Error> {
24 use tracing_appender::rolling::{RollingFileAppender, Rotation};
25 use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
26
27 let log_dir = work_dir.join("logs");
28 if !log_dir.is_dir() {
29 std::fs::create_dir_all(&log_dir)?;
30 }
31 let subscriber = tracing_subscriber::registry()
35 .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
36 .with(
37 fmt::Layer::new()
38 .with_writer(std::io::stderr)
39 .with_target(true)
40 .with_line_number(true),
41 )
42 .with(
43 fmt::Layer::new().json().with_writer(
44 RollingFileAppender::builder()
45 .rotation(Rotation::DAILY)
46 .filename_suffix("json")
48 .build(log_dir)
49 .expect("Failed to create log file"),
50 ),
51 );
52
53 subscriber.init();
54 tracing::debug!(
55 "Logging initialized (RUST_LOG={})",
56 std::env::var("RUST_LOG").unwrap_or_default()
57 );
58 Ok(())
59}
60
61pub mod atsh {
62 use crate::ssh::remote::Remotes;
63 use crate::{WORK_DIR, setup_logging};
64 use std::io::{Error, ErrorKind};
65 use std::path::Path;
66 use tracing::debug;
67
68 type Result<T> = std::result::Result<T, Error>;
69
70 pub fn initialize(work_dir: Option<&Path>) -> Result<()> {
71 let work_dir = work_dir.map(|p| p.to_path_buf()).unwrap_or({
72 let work_dir =
73 std::env::current_exe().expect("failed to get current execute directory");
74 work_dir.with_file_name(".atsh.d")
77 });
78 debug!("work_dir: {}", work_dir.display());
79
80 if !work_dir.exists() {
81 std::fs::create_dir_all(&work_dir)?;
82 }
83 setup_logging(&work_dir)?;
84
85 WORK_DIR
86 .set(work_dir)
87 .expect("WORK_DIR already initialized");
88
89 Ok(())
91 }
92
93 pub fn add(
94 user: &str,
95 password: &str,
96 ip: &str,
97 port: u16,
98 name: &Option<impl AsRef<str>>,
99 note: &Option<impl AsRef<str>>,
100 ) -> Result<usize> {
101 Remotes::add(user, password, ip, port, name, note)
102 }
103
104 pub fn remove(index: &Vec<usize>) -> Result<usize> {
105 Remotes::delete(index)
106 }
107
108 pub fn list(all: bool) -> Result<()> {
109 if all {
110 Remotes::list_all()
111 } else {
112 Remotes::list()
113 }
114 }
115
116 pub fn login(index: usize, auth: bool) -> Result<()> {
118 let remote = Remotes::get(index)?;
119 remote.login(auth)
120 }
121
122 pub fn copy(index: usize, path: &str) -> Result<()> {
123 let paths = path.split('=').collect::<Vec<&str>>();
124 if paths.len() != 2 {
125 return Err(Error::new(
126 ErrorKind::InvalidInput,
127 "path format error, like `from=to`",
128 ));
129 }
130 let remote = Remotes::get(index)?;
131 if std::path::PathBuf::from(paths[0]).exists() {
132 remote.upload(paths[0], paths[1])
133 } else {
134 remote.download(paths[0], paths[1])
135 }
136 }
137
138 pub fn upload(index: usize, path: &Vec<impl AsRef<str>>) -> Result<()> {
139 if path.len() != 2 {
140 return Err(Error::new(
141 ErrorKind::InvalidInput,
142 "path format error, like `upload -p /local/path /remote/path`",
143 ));
144 }
145 let (local, remote) = (path[0].as_ref(), path[1].as_ref());
146 if !Path::new(local).exists() {
147 return Err(Error::new(ErrorKind::NotFound, "the upload file not found"));
148 }
149
150 Remotes::get(index)?.upload(local, remote)
151 }
152
153 pub fn download(index: usize, path: &Vec<impl AsRef<str>>) -> Result<()> {
154 if path.len() != 2 {
155 return Err(Error::new(
156 ErrorKind::InvalidInput,
157 "path format error, like `upload -p /remote/path /local/path`",
158 ));
159 }
160 let (remote, local) = (path[0].as_ref(), path[1].as_ref());
161
162 Remotes::get(index)?.download(remote, local)
163 }
164}
165
166