lsh-framework 3.1.21 ā 3.1.23
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/dist/commands/sync.js +131 -2
- package/dist/lib/ipfs-client-manager.js +8 -3
- package/package.json +1 -1
package/dist/commands/sync.js
CHANGED
|
@@ -24,6 +24,9 @@ export function registerSyncCommands(program) {
|
|
|
24
24
|
// Show help when running `lsh sync` without subcommand
|
|
25
25
|
console.log(chalk.bold.cyan('\nš LSH Sync - IPFS Secrets Sync\n'));
|
|
26
26
|
console.log(chalk.gray('Sync encrypted secrets via native IPFS (no auth required)\n'));
|
|
27
|
+
console.log(chalk.bold('Quick:'));
|
|
28
|
+
console.log(` ${chalk.cyan('now')} ā” Auto-setup and push secrets in one command`);
|
|
29
|
+
console.log('');
|
|
27
30
|
console.log(chalk.bold('Setup:'));
|
|
28
31
|
console.log(` ${chalk.cyan('init')} š Full setup: install IPFS, initialize, and start daemon`);
|
|
29
32
|
console.log(` ${chalk.cyan('status')} š Show IPFS client, daemon, and sync status`);
|
|
@@ -38,8 +41,8 @@ export function registerSyncCommands(program) {
|
|
|
38
41
|
console.log(` ${chalk.cyan('clear')} šļø Clear sync history`);
|
|
39
42
|
console.log('');
|
|
40
43
|
console.log(chalk.bold('Examples:'));
|
|
41
|
-
console.log(chalk.gray(' lsh sync
|
|
42
|
-
console.log(chalk.gray(' lsh sync
|
|
44
|
+
console.log(chalk.gray(' lsh sync now # Auto-setup + push (recommended)'));
|
|
45
|
+
console.log(chalk.gray(' lsh sync now -d "v1.0" # Push with description'));
|
|
43
46
|
console.log(chalk.gray(' lsh sync pull <cid> # Pull secrets by CID'));
|
|
44
47
|
console.log('');
|
|
45
48
|
console.log(chalk.gray('Run "lsh sync <command> --help" for more info.'));
|
|
@@ -126,6 +129,132 @@ export function registerSyncCommands(program) {
|
|
|
126
129
|
console.log(chalk.cyan(' lsh sync pull <cid> # Pull secrets by CID'));
|
|
127
130
|
console.log('');
|
|
128
131
|
});
|
|
132
|
+
// lsh sync now - convenience command that does everything
|
|
133
|
+
syncCommand
|
|
134
|
+
.command('now')
|
|
135
|
+
.description('ā” Auto-setup and push secrets in one command')
|
|
136
|
+
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
137
|
+
.option('-e, --env <name>', 'Environment name', '')
|
|
138
|
+
.option('-d, --description <text>', 'Description for this sync')
|
|
139
|
+
.action(async (options) => {
|
|
140
|
+
console.log(chalk.bold.cyan('\nā” LSH Sync Now\n'));
|
|
141
|
+
const manager = new IPFSClientManager();
|
|
142
|
+
const ipfsSync = getIPFSSync();
|
|
143
|
+
const gitInfo = getGitRepoInfo();
|
|
144
|
+
// Step 1: Ensure IPFS is set up and daemon is running
|
|
145
|
+
let daemonReady = await ipfsSync.checkDaemon();
|
|
146
|
+
if (!daemonReady) {
|
|
147
|
+
// Check if IPFS is installed
|
|
148
|
+
const clientInfo = await manager.detect();
|
|
149
|
+
if (!clientInfo.installed) {
|
|
150
|
+
const installSpinner = ora('Installing IPFS client...').start();
|
|
151
|
+
try {
|
|
152
|
+
await manager.install();
|
|
153
|
+
installSpinner.succeed(chalk.green('IPFS client installed'));
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const err = error;
|
|
157
|
+
installSpinner.fail(chalk.red('Failed to install IPFS'));
|
|
158
|
+
console.error(chalk.red(err.message));
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Initialize repo if needed
|
|
163
|
+
const initSpinner = ora('Initializing IPFS...').start();
|
|
164
|
+
try {
|
|
165
|
+
await manager.init();
|
|
166
|
+
initSpinner.succeed(chalk.green('IPFS initialized'));
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const err = error;
|
|
170
|
+
if (!err.message.includes('already') && !err.message.includes('exists')) {
|
|
171
|
+
initSpinner.fail(chalk.red('Failed to initialize IPFS'));
|
|
172
|
+
console.error(chalk.red(err.message));
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
initSpinner.succeed(chalk.green('IPFS already initialized'));
|
|
176
|
+
}
|
|
177
|
+
// Start daemon
|
|
178
|
+
const startSpinner = ora('Starting IPFS daemon...').start();
|
|
179
|
+
try {
|
|
180
|
+
await manager.start();
|
|
181
|
+
// Give daemon a moment to start accepting connections
|
|
182
|
+
await new Promise(resolve => { setTimeout(resolve, 2000); });
|
|
183
|
+
startSpinner.succeed(chalk.green('IPFS daemon started'));
|
|
184
|
+
daemonReady = true;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const err = error;
|
|
188
|
+
startSpinner.fail(chalk.red('Failed to start daemon'));
|
|
189
|
+
console.error(chalk.red(err.message));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.log(chalk.green('ā IPFS daemon running'));
|
|
195
|
+
}
|
|
196
|
+
// Step 2: Read and validate .env file
|
|
197
|
+
const envPath = path.resolve(options.file);
|
|
198
|
+
if (!fs.existsSync(envPath)) {
|
|
199
|
+
console.error(chalk.red(`\nā File not found: ${envPath}`));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
203
|
+
// Step 3: Get encryption key
|
|
204
|
+
let encryptionKey = process.env[ENV_VARS.LSH_SECRETS_KEY];
|
|
205
|
+
if (!encryptionKey) {
|
|
206
|
+
const keyMatch = content.match(/^LSH_SECRETS_KEY=(.+)$/m);
|
|
207
|
+
if (keyMatch) {
|
|
208
|
+
encryptionKey = keyMatch[1].trim();
|
|
209
|
+
if ((encryptionKey.startsWith('"') && encryptionKey.endsWith('"')) ||
|
|
210
|
+
(encryptionKey.startsWith("'") && encryptionKey.endsWith("'"))) {
|
|
211
|
+
encryptionKey = encryptionKey.slice(1, -1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!encryptionKey) {
|
|
216
|
+
console.error(chalk.red('\nā LSH_SECRETS_KEY not set'));
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(chalk.gray('Generate a key with:'));
|
|
219
|
+
console.log(chalk.cyan(' lsh key'));
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
// Step 4: Encrypt and upload
|
|
223
|
+
const uploadSpinner = ora('Encrypting and uploading...').start();
|
|
224
|
+
try {
|
|
225
|
+
const key = crypto.createHash('sha256').update(encryptionKey).digest();
|
|
226
|
+
const iv = crypto.randomBytes(16);
|
|
227
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
228
|
+
let encrypted = cipher.update(content, 'utf8', 'hex');
|
|
229
|
+
encrypted += cipher.final('hex');
|
|
230
|
+
const encryptedData = iv.toString('hex') + ':' + encrypted;
|
|
231
|
+
const filename = `lsh-secrets-${options.env || 'default'}.encrypted`;
|
|
232
|
+
const cid = await ipfsSync.upload(Buffer.from(encryptedData, 'utf-8'), filename, {
|
|
233
|
+
environment: options.env || undefined,
|
|
234
|
+
gitRepo: gitInfo?.repoName || undefined,
|
|
235
|
+
});
|
|
236
|
+
if (!cid) {
|
|
237
|
+
uploadSpinner.fail(chalk.red('Upload failed'));
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
uploadSpinner.succeed(chalk.green('Synced to IPFS!'));
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log(chalk.bold('CID:'), chalk.cyan(cid));
|
|
243
|
+
if (options.description) {
|
|
244
|
+
console.log(chalk.bold('Description:'), chalk.gray(options.description));
|
|
245
|
+
}
|
|
246
|
+
console.log('');
|
|
247
|
+
console.log(chalk.gray('Pull on another machine:'));
|
|
248
|
+
console.log(chalk.cyan(` lsh sync pull ${cid}`));
|
|
249
|
+
console.log('');
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
const err = error;
|
|
253
|
+
uploadSpinner.fail(chalk.red('Sync failed'));
|
|
254
|
+
console.error(chalk.red(err.message));
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
129
258
|
// lsh sync push
|
|
130
259
|
syncCommand
|
|
131
260
|
.command('push')
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as os from 'os';
|
|
8
|
-
import { exec } from 'child_process';
|
|
8
|
+
import { exec, spawn } from 'child_process';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { createLogger } from './logger.js';
|
|
11
11
|
import { getPlatformInfo } from './platform-utils.js';
|
|
@@ -183,10 +183,15 @@ export class IPFSClientManager {
|
|
|
183
183
|
const ipfsRepoPath = path.join(this.ipfsDir, 'repo');
|
|
184
184
|
const ipfsCmd = clientInfo.path || 'ipfs';
|
|
185
185
|
try {
|
|
186
|
-
// Start daemon
|
|
187
|
-
|
|
186
|
+
// Start daemon as fully detached background process
|
|
187
|
+
// Using spawn with detached:true and stdio:'ignore' allows parent to exit
|
|
188
|
+
const daemon = spawn(ipfsCmd, ['daemon'], {
|
|
188
189
|
env: { ...process.env, IPFS_PATH: ipfsRepoPath },
|
|
190
|
+
detached: true,
|
|
191
|
+
stdio: 'ignore',
|
|
189
192
|
});
|
|
193
|
+
// Unref the child so parent process can exit independently
|
|
194
|
+
daemon.unref();
|
|
190
195
|
// Log PID for management
|
|
191
196
|
const pidPath = path.join(this.ipfsDir, 'daemon.pid');
|
|
192
197
|
if (daemon.pid) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.23",
|
|
4
4
|
"description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"bin": {
|