@fenwave/agent 1.1.0
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.
- package/.claude/settings.local.json +11 -0
- package/Dockerfile +12 -0
- package/LICENSE +29 -0
- package/README.md +434 -0
- package/auth.js +276 -0
- package/cli-commands.js +1185 -0
- package/containerManager.js +385 -0
- package/convert-to-esm.sh +62 -0
- package/docker-actions/apps.js +3256 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +346 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +188 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +696 -0
- package/helper-functions.js +193 -0
- package/index.html +60 -0
- package/index.js +988 -0
- package/package.json +49 -0
- package/setup/setupWizard.js +499 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +174 -0
- package/store/deviceCredentialStore.js +107 -0
- package/store/npmTokenStore.js +65 -0
- package/store/registryStore.js +329 -0
- package/store/setupState.js +147 -0
- package/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +54 -0
- package/utils/errorHandler.js +327 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/websocket-server.js +364 -0
package/auth.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const AGENT_ROOT_DIR = path.join(
|
|
10
|
+
os.homedir(),
|
|
11
|
+
process.env.AGENT_ROOT_DIR || '.fenwave'
|
|
12
|
+
);
|
|
13
|
+
const SESSION_DIR = process.env.SESSION_DIR || 'session';
|
|
14
|
+
const SESSION_FILE = process.env.SESSION_FILE || 'config.json';
|
|
15
|
+
const CONFIG_FILE = path.join(AGENT_ROOT_DIR, SESSION_DIR, SESSION_FILE);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensures the configuration directory exists
|
|
19
|
+
*/
|
|
20
|
+
function ensureConfigDirectory() {
|
|
21
|
+
const sessionDir = path.dirname(CONFIG_FILE);
|
|
22
|
+
if (!fs.existsSync(sessionDir)) {
|
|
23
|
+
fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Saves session data to local config file
|
|
29
|
+
* @param {string} token - Session token
|
|
30
|
+
* @param {string} expiresAt - Expiration timestamp
|
|
31
|
+
* @param {string} userEntityRef - User entity reference
|
|
32
|
+
* @param {string} backendUrl - Optional backend URL to save
|
|
33
|
+
*/
|
|
34
|
+
function saveSession(token, expiresAt, userEntityRef, backendUrl = null) {
|
|
35
|
+
ensureConfigDirectory();
|
|
36
|
+
const config = {
|
|
37
|
+
token,
|
|
38
|
+
expiresAt,
|
|
39
|
+
userEntityRef,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Preserve existing backend URL or save new one
|
|
43
|
+
if (backendUrl) {
|
|
44
|
+
config.backendUrl = backendUrl;
|
|
45
|
+
} else {
|
|
46
|
+
const existingConfig = loadSession();
|
|
47
|
+
if (existingConfig && existingConfig.backendUrl) {
|
|
48
|
+
config.backendUrl = existingConfig.backendUrl;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
|
53
|
+
mode: 0o600,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Loads session data from local config file
|
|
59
|
+
* @returns {Object|null} Session data or null if not found
|
|
60
|
+
*/
|
|
61
|
+
function loadSession() {
|
|
62
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
67
|
+
return config;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(chalk.red('❌ Error loading session config:'), error.message);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks if a session is still valid (not expired)
|
|
76
|
+
* @param {Object} session - Session data
|
|
77
|
+
* @returns {boolean} - True if session is valid
|
|
78
|
+
*/
|
|
79
|
+
function isSessionValid(session) {
|
|
80
|
+
if (!session || !session.token || !session.expiresAt) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const now = new Date();
|
|
84
|
+
const expiresAt = new Date(session.expiresAt);
|
|
85
|
+
return now < expiresAt;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handles session expiry by clearing session and exiting process
|
|
90
|
+
* @param {string} message - Optional message to display
|
|
91
|
+
* @param {Object} server - Optional WebSocket server to close
|
|
92
|
+
* @param {Object} wss - Optional WebSocket server instance to close
|
|
93
|
+
*/
|
|
94
|
+
function handleSessionExpiry(
|
|
95
|
+
message = 'Session has expired!',
|
|
96
|
+
server = null,
|
|
97
|
+
wss = null
|
|
98
|
+
) {
|
|
99
|
+
console.log(chalk.red(`❌ ${message}`));
|
|
100
|
+
console.log(chalk.red('🔒 Fenwave Agent shutting down gracefully...'));
|
|
101
|
+
|
|
102
|
+
// Close WebSocket connections and server if provided
|
|
103
|
+
if (wss) {
|
|
104
|
+
wss.clients.forEach((client) => {
|
|
105
|
+
client.send(
|
|
106
|
+
JSON.stringify({
|
|
107
|
+
type: 'session_expired',
|
|
108
|
+
message: 'Session has expired. Please re-authenticate.',
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
client.close();
|
|
112
|
+
});
|
|
113
|
+
wss.close();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (server) {
|
|
117
|
+
server.close();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
clearSession();
|
|
121
|
+
console.log(chalk.yellow("💡 Please run 'fenwave login' to authenticate again."));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a new session with the Backstage backend
|
|
127
|
+
* @param {string} jwt - Token for authentication
|
|
128
|
+
* @param {string} backendUrl - Backend URL
|
|
129
|
+
* @returns {Object} Session data with token, userEntityRef and expiresAt
|
|
130
|
+
*/
|
|
131
|
+
async function createSession(jwt, backendUrl) {
|
|
132
|
+
try {
|
|
133
|
+
const response = await axios.post(
|
|
134
|
+
`${backendUrl}/api/agent-cli/create-session`,
|
|
135
|
+
{},
|
|
136
|
+
{
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${jwt}`,
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
console.log(chalk.green('✅ Session created successfully!'));
|
|
144
|
+
const { token, expiresAt } = response.data;
|
|
145
|
+
return { token, expiresAt };
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(
|
|
148
|
+
chalk.red('❌ Failed to create session: '),
|
|
149
|
+
error.response?.data || error.message
|
|
150
|
+
);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Clears the stored session
|
|
157
|
+
*/
|
|
158
|
+
function clearSession() {
|
|
159
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
160
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Sets up a file watcher to monitor session file changes
|
|
168
|
+
* @param {Function} onSessionExpired - Callback function to handle session expiry
|
|
169
|
+
* @param {Object} server - WebSocket server instance
|
|
170
|
+
* @param {Object} wss - WebSocket server instance
|
|
171
|
+
* @returns {Object} File watcher instance
|
|
172
|
+
*/
|
|
173
|
+
function setupSessionWatcher(onSessionExpired, server = null, wss = null) {
|
|
174
|
+
let watcher = null;
|
|
175
|
+
let expiryTimer = null;
|
|
176
|
+
|
|
177
|
+
// Set up timer for natural expiry
|
|
178
|
+
const session = loadSession();
|
|
179
|
+
if (session && session.expiresAt) {
|
|
180
|
+
const expiryTime = new Date(session.expiresAt).getTime();
|
|
181
|
+
const currentTime = Date.now();
|
|
182
|
+
const timeUntilExpiry = expiryTime - currentTime;
|
|
183
|
+
|
|
184
|
+
if (timeUntilExpiry > 0) {
|
|
185
|
+
expiryTimer = setTimeout(() => {
|
|
186
|
+
clearSession();
|
|
187
|
+
onSessionExpired('Session expired !', server, wss);
|
|
188
|
+
}, timeUntilExpiry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
// Watch the config file for changes/deletion
|
|
194
|
+
watcher = fs.watch(CONFIG_FILE, (eventType, filename) => {
|
|
195
|
+
// Clear the natural expiry timer since file changed
|
|
196
|
+
if (expiryTimer) {
|
|
197
|
+
clearTimeout(expiryTimer);
|
|
198
|
+
expiryTimer = null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if file was deleted or renamed (session revoked by logout)
|
|
202
|
+
if (eventType === 'rename' || !fs.existsSync(CONFIG_FILE)) {
|
|
203
|
+
if (watcher) {
|
|
204
|
+
watcher.close();
|
|
205
|
+
}
|
|
206
|
+
onSessionExpired('Session revoked !', server, wss);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
close: () => {
|
|
212
|
+
if (watcher) watcher.close();
|
|
213
|
+
if (expiryTimer) clearTimeout(expiryTimer);
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
// Clear expiry timer if file watcher fails
|
|
218
|
+
if (expiryTimer) {
|
|
219
|
+
clearTimeout(expiryTimer);
|
|
220
|
+
expiryTimer = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Fallback to periodic checking if file watching fails
|
|
224
|
+
const intervalId = setInterval(() => {
|
|
225
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
226
|
+
clearInterval(intervalId);
|
|
227
|
+
onSessionExpired('Session revoked !', server, wss);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const currentSession = loadSession();
|
|
232
|
+
if (!currentSession || !isSessionValid(currentSession)) {
|
|
233
|
+
clearInterval(intervalId);
|
|
234
|
+
onSessionExpired('Session expired !', server, wss);
|
|
235
|
+
}
|
|
236
|
+
}, 30 * 1000); // Check every 30 seconds as fallback
|
|
237
|
+
|
|
238
|
+
return { close: () => clearInterval(intervalId) };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Loads backend URL from agent config or environment variable
|
|
244
|
+
* NOTE: This function loads synchronously from the config file
|
|
245
|
+
* @returns {string|undefined} Backend URL
|
|
246
|
+
*/
|
|
247
|
+
function loadBackendUrl() {
|
|
248
|
+
// Load directly from config file to avoid circular dependencies
|
|
249
|
+
const configPath = path.join(os.homedir(), '.fenwave', 'config', 'agent.json');
|
|
250
|
+
|
|
251
|
+
// Try to load from config file
|
|
252
|
+
if (fs.existsSync(configPath)) {
|
|
253
|
+
try {
|
|
254
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
255
|
+
if (config.backendUrl) {
|
|
256
|
+
return config.backendUrl;
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
// Fall through to env variable
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Fall back to environment variable
|
|
264
|
+
return process.env.BACKEND_URL || 'http://localhost:7007';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export {
|
|
268
|
+
saveSession,
|
|
269
|
+
loadSession,
|
|
270
|
+
isSessionValid,
|
|
271
|
+
handleSessionExpiry,
|
|
272
|
+
createSession,
|
|
273
|
+
clearSession,
|
|
274
|
+
setupSessionWatcher,
|
|
275
|
+
loadBackendUrl,
|
|
276
|
+
};
|