wative 1.1.16 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/lib/account.d.ts +14 -0
  2. package/lib/account.d.ts.map +1 -0
  3. package/lib/assets.d.ts +11 -0
  4. package/lib/assets.d.ts.map +1 -0
  5. package/lib/chain.d.ts +6 -0
  6. package/lib/chain.d.ts.map +1 -0
  7. package/lib/config.d.ts +23 -0
  8. package/lib/config.d.ts.map +1 -0
  9. package/lib/daemon-watcher.js +181 -0
  10. package/lib/home_page.d.ts +4 -0
  11. package/lib/home_page.d.ts.map +1 -0
  12. package/lib/index.d.ts +2 -0
  13. package/lib/index.d.ts.map +1 -0
  14. package/lib/index.esm.js +1 -1
  15. package/lib/index.umd.js +1 -1
  16. package/lib/network.d.ts +28 -0
  17. package/lib/network.d.ts.map +1 -0
  18. package/lib/ssh/client.d.ts +120 -0
  19. package/lib/ssh/client.d.ts.map +1 -0
  20. package/lib/ssh/client_example.d.ts +19 -0
  21. package/lib/ssh/client_example.d.ts.map +1 -0
  22. package/lib/ssh/client_test.d.ts +27 -0
  23. package/lib/ssh/client_test.d.ts.map +1 -0
  24. package/lib/ssh/config_manager.d.ts +55 -0
  25. package/lib/ssh/config_manager.d.ts.map +1 -0
  26. package/lib/ssh/config_template.d.ts +52 -0
  27. package/lib/ssh/config_template.d.ts.map +1 -0
  28. package/lib/ssh/index.d.ts +7 -0
  29. package/lib/ssh/index.d.ts.map +1 -0
  30. package/lib/ssh/remote_server_example.d.ts +16 -0
  31. package/lib/ssh/remote_server_example.d.ts.map +1 -0
  32. package/lib/ssh/service_manager.d.ts +119 -0
  33. package/lib/ssh/service_manager.d.ts.map +1 -0
  34. package/lib/ssh/types.d.ts +45 -0
  35. package/lib/ssh/types.d.ts.map +1 -0
  36. package/lib/ssh/utils.d.ts +11 -0
  37. package/lib/ssh/utils.d.ts.map +1 -0
  38. package/lib/ssh/wative_server.d.ts +26 -0
  39. package/lib/ssh/wative_server.d.ts.map +1 -0
  40. package/lib/tools.d.ts +6 -0
  41. package/lib/tools.d.ts.map +1 -0
  42. package/lib/tx_gas_utils.d.ts +18 -0
  43. package/lib/tx_gas_utils.d.ts.map +1 -0
  44. package/lib/utils.d.ts +49 -0
  45. package/lib/utils.d.ts.map +1 -0
  46. package/lib/wative.d.ts +2 -0
  47. package/lib/wative.d.ts.map +1 -0
  48. package/lib/wative.esm.js +1 -1
  49. package/lib/wative.umd.js +1 -1
  50. package/lib/web3.d.ts +59 -0
  51. package/lib/web3.d.ts.map +1 -0
  52. package/package.json +9 -5
  53. package/src/daemon-watcher.js +181 -0
  54. package/src/ssh/client.rs +221 -0
  55. package/src/ssh/client.ts +389 -0
  56. package/src/ssh/config_manager.ts +317 -0
  57. package/src/ssh/index.ts +49 -36
  58. package/src/ssh/service_manager.ts +684 -0
  59. package/src/ssh/types.ts +35 -1
  60. package/src/ssh/wative_server.ts +1 -2
  61. package/src/wative.ts +560 -122
  62. package/bin/wative-ssh.sh +0 -44
  63. package/lib/wative-ssh.esm.js +0 -1
  64. package/lib/wative-ssh.umd.js +0 -1
@@ -0,0 +1,181 @@
1
+ const { spawn } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const crypto = require('crypto');
5
+
6
+ // Get configuration from command line arguments
7
+ const serviceName = process.argv[2];
8
+ const logFile = process.argv[3];
9
+ const pidFile = process.argv[4];
10
+ const watcherPidFile = process.argv[5];
11
+ const wativeRoot = process.argv[6];
12
+
13
+ // Get encrypted passwords from environment variable and immediately delete it
14
+ const encryptedPasswords = process.env.WATIVE_ENCRYPTED_PASSWORDS;
15
+ delete process.env.WATIVE_ENCRYPTED_PASSWORDS;
16
+
17
+ if (!encryptedPasswords) {
18
+ console.error('Error: WATIVE_ENCRYPTED_PASSWORDS environment variable not found');
19
+ process.exit(1);
20
+ }
21
+
22
+ let childProcess = null;
23
+ let restartCount = 0;
24
+ const maxRestarts = 10;
25
+ const restartDelay = 5000; // 5 seconds
26
+
27
+ // Hash verification for wative.umd.js
28
+ let initialFileHash = null;
29
+ const wativeUmdPath = path.join(wativeRoot, 'lib', 'wative.umd.js');
30
+
31
+ /**
32
+ * Calculate SHA256 hash of a file
33
+ * @param {string} filePath - Path to the file
34
+ * @returns {string|null} - Hash string or null if file doesn't exist
35
+ */
36
+ const calculateFileHash = (filePath) => {
37
+ try {
38
+ if (!fs.existsSync(filePath)) {
39
+ return null;
40
+ }
41
+ const fileBuffer = fs.readFileSync(filePath);
42
+ const hashSum = crypto.createHash('sha256');
43
+ hashSum.update(fileBuffer);
44
+ return hashSum.digest('hex');
45
+ } catch (error) {
46
+ logToFile(`Error calculating file hash: ${error.message}`);
47
+ return null;
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Verify if the current file hash matches the initial hash
53
+ * @returns {boolean} - True if hashes match or initial hash is not set
54
+ */
55
+ const verifyFileHash = () => {
56
+ if (!initialFileHash) {
57
+ return true; // No initial hash to compare against
58
+ }
59
+
60
+ const currentHash = calculateFileHash(wativeUmdPath);
61
+ if (!currentHash) {
62
+ logToFile(`Warning: Could not calculate current hash for ${wativeUmdPath}`);
63
+ return false;
64
+ }
65
+
66
+ const hashesMatch = currentHash === initialFileHash;
67
+ if (!hashesMatch) {
68
+ logToFile(`File hash mismatch detected!`);
69
+ logToFile(`Initial hash: ${initialFileHash}`);
70
+ logToFile(`Current hash: ${currentHash}`);
71
+ }
72
+
73
+ return hashesMatch;
74
+ };
75
+
76
+ /**
77
+ * Log message to daemon log file with timestamp
78
+ * @param {string} message - Message to log
79
+ */
80
+ const logToFile = (message) => {
81
+ const timestamp = new Date().toISOString();
82
+ const logMessage = `[${timestamp}] [WATCHER] ${message}\n`;
83
+ fs.appendFileSync(logFile, logMessage);
84
+ };
85
+
86
+ /**
87
+ * Start the SSH service process
88
+ */
89
+ const startService = () => {
90
+ if (restartCount >= maxRestarts) {
91
+ logToFile(`Maximum restart attempts (${maxRestarts}) reached. Stopping watcher.`);
92
+ process.exit(1);
93
+ }
94
+
95
+ // Initialize or verify file hash
96
+ if (initialFileHash === null) {
97
+ // First startup - calculate and store the initial hash
98
+ initialFileHash = calculateFileHash(wativeUmdPath);
99
+ if (initialFileHash) {
100
+ logToFile(`Initial file hash calculated: ${initialFileHash}`);
101
+ } else {
102
+ logToFile(`Warning: Could not calculate initial hash for ${wativeUmdPath}`);
103
+ }
104
+ } else {
105
+ // Restart - verify hash before proceeding
106
+ if (!verifyFileHash()) {
107
+ logToFile(`File integrity check failed. Exiting daemon to prevent potential security issues.`);
108
+ process.exit(1);
109
+ }
110
+ logToFile(`File integrity check passed.`);
111
+ }
112
+
113
+ logToFile(`Starting SSH service '${serviceName}' (attempt ${restartCount + 1})`);
114
+
115
+ // Spawn the SSH service process
116
+ childProcess = spawn('npx', ['ts-node', path.join(wativeRoot, 'src', 'wative.ts'), 'ssh', 'start', '-c', serviceName], {
117
+ stdio: ['ignore', fs.openSync(logFile, 'a'), fs.openSync(logFile, 'a')],
118
+ env: {
119
+ ...process.env,
120
+ WATIVE_DAEMON_PASSWORDS: encryptedPasswords,
121
+ WATIVE_WORKER_MODE: 'true'
122
+ },
123
+ cwd: wativeRoot
124
+ });
125
+
126
+ // Write service PID to file
127
+ fs.writeFileSync(pidFile, childProcess.pid.toString());
128
+
129
+ // Handle service process exit
130
+ childProcess.on('exit', (code, signal) => {
131
+ logToFile(`SSH service exited with code ${code}, signal ${signal}`);
132
+
133
+ // Check if service crashed (non-zero exit code and not terminated by user)
134
+ if (code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGINT') {
135
+ restartCount++;
136
+ logToFile(`Service crashed. Restarting in ${restartDelay/1000} seconds...`);
137
+ setTimeout(startService, restartDelay);
138
+ } else {
139
+ logToFile('Service stopped normally.');
140
+ // Clean up PID files
141
+ try {
142
+ fs.unlinkSync(pidFile);
143
+ fs.unlinkSync(watcherPidFile);
144
+ } catch (e) {
145
+ // Ignore cleanup errors
146
+ }
147
+ process.exit(0);
148
+ }
149
+ });
150
+
151
+ // Handle service process errors
152
+ childProcess.on('error', (error) => {
153
+ logToFile(`Failed to start service: ${error.message}`);
154
+ restartCount++;
155
+ setTimeout(startService, restartDelay);
156
+ });
157
+ };
158
+
159
+ /**
160
+ * Handle graceful shutdown signals
161
+ * @param {string} signal - Signal received
162
+ */
163
+ const gracefulShutdown = (signal) => {
164
+ logToFile(`Watcher received ${signal}, stopping service...`);
165
+ if (childProcess) {
166
+ childProcess.kill('SIGTERM');
167
+ // Force kill after 5 seconds if process doesn't terminate gracefully
168
+ setTimeout(() => {
169
+ if (childProcess && !childProcess.killed) {
170
+ childProcess.kill('SIGKILL');
171
+ }
172
+ }, 5000);
173
+ }
174
+ };
175
+
176
+ // Register signal handlers for graceful shutdown
177
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
178
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
179
+
180
+ // Start the service
181
+ startService();
@@ -0,0 +1,221 @@
1
+ use crate::setting::Settings;
2
+ use std::error;
3
+ use std::io;
4
+ use std::net::{TcpStream, ToSocketAddrs};
5
+ use std::path::Path;
6
+ use std::sync::Arc;
7
+
8
+ use async_executor::{Executor, LocalExecutor, Task};
9
+ use async_io::Async;
10
+ use easy_parallel::Parallel;
11
+ use futures::executor::block_on;
12
+ use futures::future::FutureExt;
13
+ use futures::select;
14
+ use futures::{AsyncReadExt, AsyncWriteExt};
15
+
16
+ #[cfg(not(unix))]
17
+ use std::net::TcpListener;
18
+ #[cfg(unix)]
19
+ use std::os::unix::net::{UnixListener, UnixStream};
20
+ #[cfg(unix)]
21
+ use tempfile::tempdir;
22
+
23
+ use async_ssh2_lite::AsyncSession;
24
+
25
+ async fn run(ex: Arc<Executor<'_>>, command: String) -> Result<String, Box<dyn error::Error>> {
26
+ let ssh_config = &Settings::get().ssh2;
27
+
28
+ let use_bastion = ssh_config.use_bastion;
29
+
30
+ let addr = ssh_config.addr.to_owned();
31
+ let username = ssh_config.username.to_owned();
32
+
33
+ let bastion_addr;
34
+ let bastion_username;
35
+
36
+ if use_bastion {
37
+ bastion_addr = ssh_config.bastion_addr.to_owned();
38
+ bastion_username = ssh_config.bastion_username.to_owned();
39
+ } else {
40
+ bastion_addr = ssh_config.addr.to_owned();
41
+ bastion_username = ssh_config.username.to_owned();
42
+ }
43
+
44
+ let passphrase = ssh_config.passphrase.to_owned();
45
+ let addr = addr.to_socket_addrs().unwrap().next().unwrap();
46
+ let bastion_addr = bastion_addr.to_socket_addrs().unwrap().next().unwrap();
47
+
48
+ let public_key_path_str = String::from(ssh_config.public_key_path.to_owned());
49
+ let private_key_path_str = String::from(ssh_config.private_key_path.to_owned());
50
+
51
+ let task_with_main = ex.clone().spawn(async move {
52
+ let public_key_path = Path::new(&public_key_path_str);
53
+ let private_key_path = Path::new(&private_key_path_str);
54
+ let bastion_stream = Async::<TcpStream>::connect(bastion_addr).await?;
55
+
56
+ let mut bastion_session = AsyncSession::new(bastion_stream, None)?;
57
+
58
+ bastion_session.handshake().await?;
59
+
60
+ bastion_session
61
+ .userauth_pubkey_file(
62
+ &bastion_username,
63
+ Some(public_key_path),
64
+ private_key_path,
65
+ Some(&passphrase),
66
+ )
67
+ .await?;
68
+
69
+ if !bastion_session.authenticated() {
70
+ return Err(bastion_session
71
+ .last_error()
72
+ .map( io::Error::from)
73
+ .unwrap_or_else(||io::Error::new(
74
+ io::ErrorKind::Other,
75
+ "bastion unknown userauth error",
76
+ )));
77
+ }
78
+
79
+ if !use_bastion {
80
+ let mut channel = bastion_session.channel_session().await?;
81
+
82
+ channel.exec(&command).await?;
83
+ let mut res = String::new();
84
+ channel.read_to_string(&mut res).await?;
85
+ channel.close().await?;
86
+
87
+ bastion_session.disconnect(None, "foo", None).await?;
88
+
89
+ return Ok(res);
90
+ }
91
+
92
+ let mut bastion_channel = bastion_session
93
+ .channel_direct_tcpip(addr.ip().to_string().as_ref(), addr.port(), None)
94
+ .await?;
95
+
96
+ let (forward_stream_s, mut forward_stream_r) = {
97
+ cfg_if::cfg_if! {
98
+ if #[cfg(unix)] {
99
+ let dir = tempdir()?;
100
+ let path = dir.path().join("ssh_channel_direct_tcpip");
101
+ let listener = Async::<UnixListener>::bind(&path)?;
102
+ let stream_s = Async::<UnixStream>::connect(&path).await?;
103
+ } else {
104
+ let listen_addr = TcpListener::bind("localhost:0")
105
+ .unwrap()
106
+ .local_addr()
107
+ .unwrap();
108
+ let listener = Async::<TcpListener>::bind(listen_addr)?;
109
+ let stream_s = Async::<TcpStream>::connect(listen_addr).await?;
110
+ }
111
+ }
112
+
113
+ let (stream_r,_) = listener.accept().await.unwrap();
114
+
115
+ (stream_s, stream_r)
116
+ };
117
+
118
+ let task_with_forward: Task<io::Result<()>> = ex.clone().spawn(async move {
119
+ let mut buf_bastion_channel = vec![0; 2048];
120
+ let mut buf_forward_stream_r = vec![0; 2048];
121
+
122
+ loop {
123
+ select! {
124
+ ret_forward_stream_r = forward_stream_r.read(&mut buf_forward_stream_r).fuse() => match ret_forward_stream_r {
125
+ Ok(n) if n == 0 => {
126
+ break
127
+ },
128
+ Ok(n) => {
129
+ bastion_channel.write(&buf_forward_stream_r[..n]).await.map(|_| ()).map_err(|err| {
130
+ err
131
+ })?
132
+ },
133
+ Err(err) => {
134
+ return Err(err);
135
+ }
136
+ },
137
+ ret_bastion_channel = bastion_channel.read(&mut buf_bastion_channel).fuse() => match ret_bastion_channel {
138
+ Ok(n) if n == 0 => {
139
+ break
140
+ },
141
+ Ok(n) => {
142
+ forward_stream_r.write(&buf_bastion_channel[..n]).await.map(|_| ()).map_err(|err| {
143
+ err
144
+ })?
145
+ },
146
+ Err(err) => {
147
+ return Err(err);
148
+ }
149
+ },
150
+ }
151
+ }
152
+
153
+ Ok(())
154
+ });
155
+ task_with_forward.detach();
156
+
157
+ let mut session = AsyncSession::new(forward_stream_s, None)?;
158
+ session.handshake().await?;
159
+
160
+ session.userauth_pubkey_file(
161
+ &username,
162
+ Some(public_key_path),
163
+ private_key_path,
164
+ Some(&passphrase),
165
+ )
166
+ .await?;
167
+
168
+ if !session.authenticated() {
169
+ return Err(session
170
+ .last_error()
171
+ .map(io::Error::from)
172
+ .unwrap_or_else(||io::Error::new(
173
+ io::ErrorKind::Other,
174
+ "unknown userauth error",
175
+ )));
176
+ }
177
+
178
+ let mut channel = session.channel_session().await?;
179
+
180
+ channel.exec(&command).await?;
181
+ let mut res = String::new();
182
+ channel.read_to_string(&mut res).await?;
183
+ channel.close().await?;
184
+
185
+ session.disconnect(None, "foo", None).await?;
186
+
187
+ Ok(res)
188
+ });
189
+
190
+ let result = task_with_main.await.map(|message| message).map_err(|err| {
191
+ eprintln!("task_with_main run failed, err:{err:?}");
192
+
193
+ err
194
+ })?;
195
+
196
+ let signed_data = json::parse(&result).unwrap();
197
+ if signed_data["status"] == false {
198
+ panic!("{}", signed_data["msg"].to_string())
199
+ }
200
+
201
+ Ok(json::stringify(signed_data["data"].clone()))
202
+ }
203
+
204
+ pub fn ssh2_call(command: String) -> Result<String, Box<dyn error::Error>> {
205
+ let ex = Executor::new();
206
+ let ex = Arc::new(ex);
207
+ let local_ex = LocalExecutor::new();
208
+ let (trigger, shutdown) = async_channel::unbounded::<()>();
209
+
210
+ let ret_vec = Parallel::new()
211
+ .each(0..4, |_| block_on(ex.run(async { shutdown.recv().await })))
212
+ .finish(|| {
213
+ block_on(local_ex.run(async {
214
+ let result = run(ex.clone(), command).await.unwrap();
215
+ drop(trigger);
216
+ result
217
+ }))
218
+ });
219
+
220
+ Ok(ret_vec.1)
221
+ }