@oussema_mili/test-pkg-123 1.1.32
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/LICENSE +29 -0
- package/README.md +220 -0
- package/auth-callback.html +97 -0
- package/auth.js +276 -0
- package/cli-commands.js +1921 -0
- package/containerManager.js +304 -0
- package/daemon/agentRunner.js +491 -0
- package/daemon/daemonEntry.js +64 -0
- package/daemon/daemonManager.js +266 -0
- package/daemon/logManager.js +227 -0
- package/dist/styles.css +504 -0
- package/docker-actions/apps.js +3913 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +355 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +224 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/setup-tasks.js +859 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +713 -0
- package/helper-functions.js +193 -0
- package/index.html +83 -0
- package/index.js +341 -0
- package/package.json +82 -0
- package/postcss.config.mjs +5 -0
- package/scripts/release.sh +212 -0
- package/setup/setupWizard.js +403 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +171 -0
- package/store/daemonStore.js +217 -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/styles.css +1 -0
- package/utils/appLogger.js +223 -0
- package/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +41 -0
- package/utils/errorHandler.js +327 -0
- package/utils/portUtils.js +59 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/utils/ssl-certificates.js +256 -0
- package/websocket-server.js +415 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { loadConfig } from './configStore.js';
|
|
5
|
+
|
|
6
|
+
const fsPromises = fs.promises;
|
|
7
|
+
|
|
8
|
+
// Load configuration
|
|
9
|
+
const config = loadConfig();
|
|
10
|
+
const AGENT_ROOT_DIR = config.agentRootDir;
|
|
11
|
+
const AGENT_INFO_DIR = 'agent';
|
|
12
|
+
const AGENT_INFO_FILE = 'info.json';
|
|
13
|
+
const AGENT_INFO_PATH = path.join(
|
|
14
|
+
os.homedir(),
|
|
15
|
+
AGENT_ROOT_DIR,
|
|
16
|
+
AGENT_INFO_DIR,
|
|
17
|
+
AGENT_INFO_FILE
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Agent Store for managing persistent agent information
|
|
22
|
+
*/
|
|
23
|
+
class AgentStore {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.agentInfo = null;
|
|
26
|
+
this.initialized = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the store
|
|
31
|
+
*/
|
|
32
|
+
async initialize() {
|
|
33
|
+
if (this.initialized) return;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Ensure directory exists
|
|
37
|
+
await fsPromises.mkdir(path.dirname(AGENT_INFO_PATH), { recursive: true });
|
|
38
|
+
this.initialized = true;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Failed to initialize agent store:', error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Save agent start time
|
|
47
|
+
*/
|
|
48
|
+
async saveAgentStartTime(startTime) {
|
|
49
|
+
await this.initialize();
|
|
50
|
+
|
|
51
|
+
const agentInfo = {
|
|
52
|
+
startTime: startTime.toISOString(),
|
|
53
|
+
lastUpdated: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await fsPromises.writeFile(AGENT_INFO_PATH, JSON.stringify(agentInfo, null, 2));
|
|
58
|
+
this.agentInfo = agentInfo;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Failed to save agent start time:', error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Load agent start time
|
|
67
|
+
*/
|
|
68
|
+
async loadAgentStartTime() {
|
|
69
|
+
await this.initialize();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const data = await fsPromises.readFile(AGENT_INFO_PATH, 'utf8');
|
|
73
|
+
const agentInfo = JSON.parse(data);
|
|
74
|
+
this.agentInfo = agentInfo;
|
|
75
|
+
return new Date(agentInfo.startTime);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error.code === 'ENOENT') {
|
|
78
|
+
// File doesn't exist - agent not running or first time
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
console.error('Failed to load agent start time:', error);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clear agent info (when agent stops)
|
|
88
|
+
*/
|
|
89
|
+
async clearAgentInfo() {
|
|
90
|
+
await this.initialize();
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await fsPromises.unlink(AGENT_INFO_PATH);
|
|
94
|
+
this.agentInfo = null;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error.code !== 'ENOENT') {
|
|
97
|
+
console.error('Failed to clear agent info:', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if agent is currently running based on stored info
|
|
104
|
+
*/
|
|
105
|
+
async isAgentRunning() {
|
|
106
|
+
const startTime = await this.loadAgentStartTime();
|
|
107
|
+
return startTime !== null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Export singleton instance
|
|
112
|
+
const agentStore = new AgentStore();
|
|
113
|
+
export default agentStore;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
const FENWAVE_DIR = path.join(os.homedir(), ".fenwave");
|
|
6
|
+
const CONFIG_DIR = path.join(FENWAVE_DIR, "config");
|
|
7
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "agent.json");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default configuration values
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
// Fenwave URLs
|
|
14
|
+
backendUrl: "http://localhost:7007",
|
|
15
|
+
frontendUrl: "http://localhost:3000",
|
|
16
|
+
|
|
17
|
+
// Container Configuration
|
|
18
|
+
containerName: "fenwave-devapp",
|
|
19
|
+
containerPort: 3003,
|
|
20
|
+
wsPort: 3001,
|
|
21
|
+
|
|
22
|
+
// Directory Configuration
|
|
23
|
+
agentRootDir: ".fenwave",
|
|
24
|
+
registriesDir: "registries",
|
|
25
|
+
containerDataDir: "/data",
|
|
26
|
+
|
|
27
|
+
// Docker Image (GHCR - public registry, no auth required)
|
|
28
|
+
dockerImage: "ghcr.io/fenleap/fenwave/dev-app:latest",
|
|
29
|
+
|
|
30
|
+
// Other Settings
|
|
31
|
+
authTimeoutMs: 60000,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensure config directory exists
|
|
36
|
+
*/
|
|
37
|
+
function ensureConfigDir() {
|
|
38
|
+
if (!fs.existsSync(FENWAVE_DIR)) {
|
|
39
|
+
fs.mkdirSync(FENWAVE_DIR, { recursive: true, mode: 0o700 });
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
42
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load configuration from disk
|
|
48
|
+
* @returns {Object} Configuration object with defaults applied
|
|
49
|
+
*/
|
|
50
|
+
export function loadConfig() {
|
|
51
|
+
let config = { ...DEFAULT_CONFIG };
|
|
52
|
+
|
|
53
|
+
// Load from file if exists
|
|
54
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
55
|
+
try {
|
|
56
|
+
const fileConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
57
|
+
config = { ...config, ...fileConfig };
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("Failed to load agent config:", error.message);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fall back to environment variables if config values are not set
|
|
64
|
+
config.backendUrl =
|
|
65
|
+
config.backendUrl || process.env.BACKEND_URL || DEFAULT_CONFIG.backendUrl;
|
|
66
|
+
config.frontendUrl =
|
|
67
|
+
config.frontendUrl ||
|
|
68
|
+
process.env.FRONTEND_URL ||
|
|
69
|
+
DEFAULT_CONFIG.frontendUrl;
|
|
70
|
+
config.wsPort =
|
|
71
|
+
config.wsPort || Number(process.env.WS_PORT) || DEFAULT_CONFIG.wsPort;
|
|
72
|
+
config.containerPort =
|
|
73
|
+
config.containerPort ||
|
|
74
|
+
Number(process.env.CONTAINER_PORT) ||
|
|
75
|
+
DEFAULT_CONFIG.containerPort;
|
|
76
|
+
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Save configuration to disk
|
|
82
|
+
* @param {Object} config - Configuration object to save
|
|
83
|
+
*/
|
|
84
|
+
export function saveConfig(config) {
|
|
85
|
+
ensureConfigDir();
|
|
86
|
+
|
|
87
|
+
const configToSave = {
|
|
88
|
+
...config,
|
|
89
|
+
updatedAt: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(configToSave, null, 2), {
|
|
93
|
+
mode: 0o600,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Update specific configuration values
|
|
99
|
+
* @param {Object} updates - Configuration updates
|
|
100
|
+
*/
|
|
101
|
+
export function updateConfig(updates) {
|
|
102
|
+
const currentConfig = loadConfig();
|
|
103
|
+
const newConfig = { ...currentConfig, ...updates };
|
|
104
|
+
saveConfig(newConfig);
|
|
105
|
+
return newConfig;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a specific configuration value
|
|
110
|
+
* @param {string} key - Configuration key
|
|
111
|
+
* @param {any} defaultValue - Default value if key not found
|
|
112
|
+
* @returns {any} Configuration value
|
|
113
|
+
*/
|
|
114
|
+
export function getConfig(key, defaultValue = null) {
|
|
115
|
+
const config = loadConfig();
|
|
116
|
+
return config[key] !== undefined ? config[key] : defaultValue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if configuration file exists
|
|
121
|
+
* @returns {boolean} True if config file exists
|
|
122
|
+
*/
|
|
123
|
+
export function configExists() {
|
|
124
|
+
return fs.existsSync(CONFIG_FILE);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clear configuration file
|
|
129
|
+
*/
|
|
130
|
+
export function clearConfig() {
|
|
131
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
132
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get configuration file path
|
|
138
|
+
* @returns {string} Path to config file
|
|
139
|
+
*/
|
|
140
|
+
export function getConfigPath() {
|
|
141
|
+
return CONFIG_FILE;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Initialize configuration with values from init wizard
|
|
146
|
+
* @param {Object} initConfig - Configuration from init wizard
|
|
147
|
+
*/
|
|
148
|
+
export function initializeConfig(initConfig) {
|
|
149
|
+
const config = {
|
|
150
|
+
...DEFAULT_CONFIG,
|
|
151
|
+
...initConfig,
|
|
152
|
+
createdAt: new Date().toISOString(),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
saveConfig(config);
|
|
156
|
+
return config;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export { DEFAULT_CONFIG };
|
|
160
|
+
|
|
161
|
+
export default {
|
|
162
|
+
loadConfig,
|
|
163
|
+
saveConfig,
|
|
164
|
+
updateConfig,
|
|
165
|
+
getConfig,
|
|
166
|
+
configExists,
|
|
167
|
+
clearConfig,
|
|
168
|
+
getConfigPath,
|
|
169
|
+
initializeConfig,
|
|
170
|
+
DEFAULT_CONFIG,
|
|
171
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const FENWAVE_DIR = path.join(os.homedir(), '.fenwave');
|
|
6
|
+
const DAEMON_DIR = path.join(FENWAVE_DIR, 'daemon');
|
|
7
|
+
const STATE_FILE = path.join(DAEMON_DIR, 'state.json');
|
|
8
|
+
const PID_FILE = path.join(DAEMON_DIR, 'agent.pid');
|
|
9
|
+
const LOCK_FILE = path.join(DAEMON_DIR, 'agent.lock');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ensure daemon directory exists
|
|
13
|
+
*/
|
|
14
|
+
function ensureDaemonDir() {
|
|
15
|
+
if (!fs.existsSync(DAEMON_DIR)) {
|
|
16
|
+
fs.mkdirSync(DAEMON_DIR, { recursive: true, mode: 0o700 });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get daemon state
|
|
22
|
+
* @returns {Object|null} Daemon state or null if not found
|
|
23
|
+
*/
|
|
24
|
+
export function getDaemonState() {
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(STATE_FILE)) {
|
|
27
|
+
const data = fs.readFileSync(STATE_FILE, 'utf8');
|
|
28
|
+
return JSON.parse(data);
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// Ignore errors reading state
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Save daemon state
|
|
38
|
+
* @param {Object} state - State object to save
|
|
39
|
+
*/
|
|
40
|
+
export function saveDaemonState(state) {
|
|
41
|
+
ensureDaemonDir();
|
|
42
|
+
const stateToSave = {
|
|
43
|
+
...state,
|
|
44
|
+
updatedAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(stateToSave, null, 2), {
|
|
47
|
+
mode: 0o600,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Update daemon state with partial updates
|
|
53
|
+
* @param {Object} updates - Partial state updates
|
|
54
|
+
*/
|
|
55
|
+
export function updateDaemonState(updates) {
|
|
56
|
+
const currentState = getDaemonState() || {};
|
|
57
|
+
const newState = { ...currentState, ...updates };
|
|
58
|
+
saveDaemonState(newState);
|
|
59
|
+
return newState;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear daemon state
|
|
64
|
+
*/
|
|
65
|
+
export function clearDaemonState() {
|
|
66
|
+
try {
|
|
67
|
+
if (fs.existsSync(STATE_FILE)) {
|
|
68
|
+
fs.unlinkSync(STATE_FILE);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// Ignore errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get daemon PID
|
|
77
|
+
* @returns {number|null} PID or null if not found
|
|
78
|
+
*/
|
|
79
|
+
export function getDaemonPid() {
|
|
80
|
+
try {
|
|
81
|
+
if (fs.existsSync(PID_FILE)) {
|
|
82
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
83
|
+
return isNaN(pid) ? null : pid;
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// Ignore errors
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Save daemon PID
|
|
93
|
+
* @param {number} pid - Process ID
|
|
94
|
+
*/
|
|
95
|
+
export function saveDaemonPid(pid) {
|
|
96
|
+
ensureDaemonDir();
|
|
97
|
+
fs.writeFileSync(PID_FILE, String(pid), { mode: 0o600 });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Clear daemon PID file
|
|
102
|
+
*/
|
|
103
|
+
export function clearDaemonPid() {
|
|
104
|
+
try {
|
|
105
|
+
if (fs.existsSync(PID_FILE)) {
|
|
106
|
+
fs.unlinkSync(PID_FILE);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// Ignore errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if daemon process is running
|
|
115
|
+
* @returns {boolean} True if daemon is running
|
|
116
|
+
*/
|
|
117
|
+
export function isDaemonRunning() {
|
|
118
|
+
const pid = getDaemonPid();
|
|
119
|
+
if (!pid) return false;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// Send signal 0 to check if process exists
|
|
123
|
+
process.kill(pid, 0);
|
|
124
|
+
return true;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Process doesn't exist
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Acquire daemon lock
|
|
133
|
+
* @returns {boolean} True if lock acquired
|
|
134
|
+
*/
|
|
135
|
+
export function acquireLock() {
|
|
136
|
+
ensureDaemonDir();
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Check if lock file exists and if process is still running
|
|
140
|
+
if (fs.existsSync(LOCK_FILE)) {
|
|
141
|
+
const lockData = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf8'));
|
|
142
|
+
try {
|
|
143
|
+
process.kill(lockData.pid, 0);
|
|
144
|
+
// Process still running, cannot acquire lock
|
|
145
|
+
return false;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// Process not running, remove stale lock
|
|
148
|
+
fs.unlinkSync(LOCK_FILE);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Create lock file
|
|
153
|
+
fs.writeFileSync(
|
|
154
|
+
LOCK_FILE,
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
pid: process.pid,
|
|
157
|
+
createdAt: new Date().toISOString(),
|
|
158
|
+
}),
|
|
159
|
+
{ mode: 0o600, flag: 'wx' }
|
|
160
|
+
);
|
|
161
|
+
return true;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error.code === 'EEXIST') {
|
|
164
|
+
// Lock file was created between our check and write
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Release daemon lock
|
|
173
|
+
*/
|
|
174
|
+
export function releaseLock() {
|
|
175
|
+
try {
|
|
176
|
+
if (fs.existsSync(LOCK_FILE)) {
|
|
177
|
+
const lockData = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf8'));
|
|
178
|
+
// Only remove if we own the lock
|
|
179
|
+
if (lockData.pid === process.pid) {
|
|
180
|
+
fs.unlinkSync(LOCK_FILE);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
// Ignore errors
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get state file path (for external consumers like DevApp)
|
|
190
|
+
* @returns {string} Path to state file
|
|
191
|
+
*/
|
|
192
|
+
export function getStateFilePath() {
|
|
193
|
+
return STATE_FILE;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get daemon directory path
|
|
198
|
+
* @returns {string} Path to daemon directory
|
|
199
|
+
*/
|
|
200
|
+
export function getDaemonDir() {
|
|
201
|
+
return DAEMON_DIR;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default {
|
|
205
|
+
getDaemonState,
|
|
206
|
+
saveDaemonState,
|
|
207
|
+
updateDaemonState,
|
|
208
|
+
clearDaemonState,
|
|
209
|
+
getDaemonPid,
|
|
210
|
+
saveDaemonPid,
|
|
211
|
+
clearDaemonPid,
|
|
212
|
+
isDaemonRunning,
|
|
213
|
+
acquireLock,
|
|
214
|
+
releaseLock,
|
|
215
|
+
getStateFilePath,
|
|
216
|
+
getDaemonDir,
|
|
217
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { encrypt, decrypt } from '../utils/encryption.js';
|
|
5
|
+
|
|
6
|
+
const FENWAVE_DIR = path.join(os.homedir(), '.fenwave');
|
|
7
|
+
const DEVICE_DIR = path.join(FENWAVE_DIR, 'device');
|
|
8
|
+
const CREDENTIAL_FILE = path.join(DEVICE_DIR, 'credential.json');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ensure device directory exists
|
|
12
|
+
*/
|
|
13
|
+
function ensureDeviceDir() {
|
|
14
|
+
if (!fs.existsSync(FENWAVE_DIR)) {
|
|
15
|
+
fs.mkdirSync(FENWAVE_DIR, { recursive: true, mode: 0o700 });
|
|
16
|
+
}
|
|
17
|
+
if (!fs.existsSync(DEVICE_DIR)) {
|
|
18
|
+
fs.mkdirSync(DEVICE_DIR, { recursive: true, mode: 0o700 });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Save device credential to disk (encrypted)
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} credentialData - Device credential data
|
|
26
|
+
* @param {string} credentialData.deviceId - Device ID
|
|
27
|
+
* @param {string} credentialData.deviceCredential - Device credential
|
|
28
|
+
* @param {string} credentialData.userEntityRef - User entity reference
|
|
29
|
+
* @param {string} credentialData.deviceName - Device name
|
|
30
|
+
* @param {string} credentialData.platform - Platform
|
|
31
|
+
* @param {string} credentialData.agentVersion - Agent version
|
|
32
|
+
*/
|
|
33
|
+
export function saveDeviceCredential(credentialData) {
|
|
34
|
+
ensureDeviceDir();
|
|
35
|
+
|
|
36
|
+
const data = {
|
|
37
|
+
deviceId: credentialData.deviceId,
|
|
38
|
+
deviceCredential: encrypt(credentialData.deviceCredential), // Encrypt credential
|
|
39
|
+
userEntityRef: credentialData.userEntityRef,
|
|
40
|
+
deviceName: credentialData.deviceName,
|
|
41
|
+
platform: credentialData.platform,
|
|
42
|
+
agentVersion: credentialData.agentVersion,
|
|
43
|
+
registeredAt: new Date().toISOString(),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(CREDENTIAL_FILE, JSON.stringify(data, null, 2), {
|
|
47
|
+
mode: 0o600,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Load device credential from disk
|
|
53
|
+
*
|
|
54
|
+
* @returns {Object|null} Device credential data or null if not found
|
|
55
|
+
*/
|
|
56
|
+
export function loadDeviceCredential() {
|
|
57
|
+
if (!fs.existsSync(CREDENTIAL_FILE)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(fs.readFileSync(CREDENTIAL_FILE, 'utf8'));
|
|
63
|
+
|
|
64
|
+
// Decrypt the credential
|
|
65
|
+
const decryptedCredential = decrypt(data.deviceCredential);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
deviceId: data.deviceId,
|
|
69
|
+
deviceCredential: decryptedCredential,
|
|
70
|
+
userEntityRef: data.userEntityRef,
|
|
71
|
+
deviceName: data.deviceName,
|
|
72
|
+
platform: data.platform,
|
|
73
|
+
agentVersion: data.agentVersion,
|
|
74
|
+
registeredAt: data.registeredAt,
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Failed to load device credential:', error.message);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if device is registered
|
|
84
|
+
*
|
|
85
|
+
* @returns {boolean} True if device credential exists
|
|
86
|
+
*/
|
|
87
|
+
export function isDeviceRegistered() {
|
|
88
|
+
return fs.existsSync(CREDENTIAL_FILE);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear device credential (delete file)
|
|
93
|
+
*/
|
|
94
|
+
export function clearDeviceCredential() {
|
|
95
|
+
if (fs.existsSync(CREDENTIAL_FILE)) {
|
|
96
|
+
fs.unlinkSync(CREDENTIAL_FILE);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get device credential file path
|
|
102
|
+
*
|
|
103
|
+
* @returns {string} Path to credential file
|
|
104
|
+
*/
|
|
105
|
+
export function getCredentialFilePath() {
|
|
106
|
+
return CREDENTIAL_FILE;
|
|
107
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const FENWAVE_DIR = path.join(os.homedir(), '.fenwave');
|
|
6
|
+
const NPM_DIR = path.join(FENWAVE_DIR, 'npm');
|
|
7
|
+
const NPM_TOKEN_FILE = path.join(NPM_DIR, 'token');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ensure NPM directory exists
|
|
11
|
+
*/
|
|
12
|
+
function ensureNpmDir() {
|
|
13
|
+
if (!fs.existsSync(FENWAVE_DIR)) {
|
|
14
|
+
fs.mkdirSync(FENWAVE_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
+
}
|
|
16
|
+
if (!fs.existsSync(NPM_DIR)) {
|
|
17
|
+
fs.mkdirSync(NPM_DIR, { recursive: true, mode: 0o700 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Save NPM token to disk
|
|
23
|
+
*
|
|
24
|
+
* @param {string} token - NPM token
|
|
25
|
+
*/
|
|
26
|
+
export function saveNpmToken(token) {
|
|
27
|
+
ensureNpmDir();
|
|
28
|
+
fs.writeFileSync(NPM_TOKEN_FILE, token, { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load NPM token from disk
|
|
33
|
+
*
|
|
34
|
+
* @returns {string|null} NPM token or null if not found
|
|
35
|
+
*/
|
|
36
|
+
export function loadNpmToken() {
|
|
37
|
+
if (!fs.existsSync(NPM_TOKEN_FILE)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
return fs.readFileSync(NPM_TOKEN_FILE, 'utf8').trim();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to load NPM token:', error.message);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if NPM token exists
|
|
51
|
+
*
|
|
52
|
+
* @returns {boolean} True if NPM token is configured
|
|
53
|
+
*/
|
|
54
|
+
export function hasNpmToken() {
|
|
55
|
+
return fs.existsSync(NPM_TOKEN_FILE);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Clear NPM token (delete file)
|
|
60
|
+
*/
|
|
61
|
+
export function clearNpmToken() {
|
|
62
|
+
if (fs.existsSync(NPM_TOKEN_FILE)) {
|
|
63
|
+
fs.unlinkSync(NPM_TOKEN_FILE);
|
|
64
|
+
}
|
|
65
|
+
}
|