ctx-sync 1.0.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/dist/commands/audit.d.ts +76 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +367 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/config.d.ts +58 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +114 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/dir.d.ts +56 -0
- package/dist/commands/dir.d.ts.map +1 -0
- package/dist/commands/dir.js +172 -0
- package/dist/commands/dir.js.map +1 -0
- package/dist/commands/docker.d.ts +140 -0
- package/dist/commands/docker.d.ts.map +1 -0
- package/dist/commands/docker.js +380 -0
- package/dist/commands/docker.js.map +1 -0
- package/dist/commands/env.d.ts +96 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +352 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/init.d.ts +89 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/key.d.ts +92 -0
- package/dist/commands/key.d.ts.map +1 -0
- package/dist/commands/key.js +274 -0
- package/dist/commands/key.js.map +1 -0
- package/dist/commands/list.d.ts +38 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +84 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/note.d.ts +151 -0
- package/dist/commands/note.d.ts.map +1 -0
- package/dist/commands/note.js +411 -0
- package/dist/commands/note.js.map +1 -0
- package/dist/commands/pull.d.ts +47 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +94 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +40 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +94 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/restore.d.ts +116 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +336 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/service.d.ts +83 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +259 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/show.d.ts +63 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +243 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +53 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +150 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +105 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +243 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/team.d.ts +79 -0
- package/dist/commands/team.d.ts.map +1 -0
- package/dist/commands/team.js +233 -0
- package/dist/commands/team.js.map +1 -0
- package/dist/commands/track.d.ts +109 -0
- package/dist/commands/track.d.ts.map +1 -0
- package/dist/commands/track.js +406 -0
- package/dist/commands/track.js.map +1 -0
- package/dist/core/command-validator.d.ts +100 -0
- package/dist/core/command-validator.d.ts.map +1 -0
- package/dist/core/command-validator.js +299 -0
- package/dist/core/command-validator.js.map +1 -0
- package/dist/core/config-store.d.ts +76 -0
- package/dist/core/config-store.d.ts.map +1 -0
- package/dist/core/config-store.js +148 -0
- package/dist/core/config-store.js.map +1 -0
- package/dist/core/directories-handler.d.ts +116 -0
- package/dist/core/directories-handler.d.ts.map +1 -0
- package/dist/core/directories-handler.js +199 -0
- package/dist/core/directories-handler.js.map +1 -0
- package/dist/core/docker-handler.d.ts +183 -0
- package/dist/core/docker-handler.d.ts.map +1 -0
- package/dist/core/docker-handler.js +515 -0
- package/dist/core/docker-handler.js.map +1 -0
- package/dist/core/encryption.d.ts +79 -0
- package/dist/core/encryption.d.ts.map +1 -0
- package/dist/core/encryption.js +111 -0
- package/dist/core/encryption.js.map +1 -0
- package/dist/core/env-handler.d.ts +128 -0
- package/dist/core/env-handler.d.ts.map +1 -0
- package/dist/core/env-handler.js +272 -0
- package/dist/core/env-handler.js.map +1 -0
- package/dist/core/git-sync.d.ts +88 -0
- package/dist/core/git-sync.d.ts.map +1 -0
- package/dist/core/git-sync.js +143 -0
- package/dist/core/git-sync.js.map +1 -0
- package/dist/core/key-store.d.ts +51 -0
- package/dist/core/key-store.d.ts.map +1 -0
- package/dist/core/key-store.js +108 -0
- package/dist/core/key-store.js.map +1 -0
- package/dist/core/log-sanitizer.d.ts +72 -0
- package/dist/core/log-sanitizer.d.ts.map +1 -0
- package/dist/core/log-sanitizer.js +202 -0
- package/dist/core/log-sanitizer.js.map +1 -0
- package/dist/core/path-validator.d.ts +37 -0
- package/dist/core/path-validator.d.ts.map +1 -0
- package/dist/core/path-validator.js +127 -0
- package/dist/core/path-validator.js.map +1 -0
- package/dist/core/recipients.d.ts +99 -0
- package/dist/core/recipients.d.ts.map +1 -0
- package/dist/core/recipients.js +206 -0
- package/dist/core/recipients.js.map +1 -0
- package/dist/core/services-handler.d.ts +113 -0
- package/dist/core/services-handler.d.ts.map +1 -0
- package/dist/core/services-handler.js +176 -0
- package/dist/core/services-handler.js.map +1 -0
- package/dist/core/state-manager.d.ts +96 -0
- package/dist/core/state-manager.d.ts.map +1 -0
- package/dist/core/state-manager.js +165 -0
- package/dist/core/state-manager.js.map +1 -0
- package/dist/core/transport.d.ts +28 -0
- package/dist/core/transport.d.ts.map +1 -0
- package/dist/core/transport.js +79 -0
- package/dist/core/transport.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +81 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +191 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/secure-memory.d.ts +65 -0
- package/dist/utils/secure-memory.d.ts.map +1 -0
- package/dist/utils/secure-memory.js +86 -0
- package/dist/utils/secure-memory.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync init` command.
|
|
3
|
+
*
|
|
4
|
+
* Handles first-time setup (key generation, Git repo init, remote config)
|
|
5
|
+
* and `--restore` flow (key restoration, repo clone, state decryption).
|
|
6
|
+
*
|
|
7
|
+
* @module commands/init
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import { VERSION, SYNC_DIR, CONFIG_DIR, STATE_FILES } from '@ctx-sync/shared';
|
|
13
|
+
import { generateKey } from '../core/encryption.js';
|
|
14
|
+
import { decryptState } from '../core/encryption.js';
|
|
15
|
+
import { saveKey, loadKey } from '../core/key-store.js';
|
|
16
|
+
import { initRepo, addRemote, commitState } from '../core/git-sync.js';
|
|
17
|
+
import { validateRemoteUrl } from '../core/transport.js';
|
|
18
|
+
import { withErrorHandler } from '../utils/errors.js';
|
|
19
|
+
/**
|
|
20
|
+
* Get the config directory path.
|
|
21
|
+
* Uses CTX_SYNC_HOME env var for testing, otherwise ~/.config/ctx-sync.
|
|
22
|
+
*/
|
|
23
|
+
export function getConfigDir() {
|
|
24
|
+
const home = process.env['CTX_SYNC_HOME'] ?? os.homedir();
|
|
25
|
+
return path.join(home, '.config', CONFIG_DIR);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the sync directory path.
|
|
29
|
+
* Uses CTX_SYNC_HOME env var for testing, otherwise ~/.context-sync.
|
|
30
|
+
*/
|
|
31
|
+
export function getSyncDir() {
|
|
32
|
+
const home = process.env['CTX_SYNC_HOME'] ?? os.homedir();
|
|
33
|
+
return path.join(home, SYNC_DIR);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create the initial manifest.json in the sync directory.
|
|
37
|
+
*
|
|
38
|
+
* The manifest is the only plaintext file in the sync repo.
|
|
39
|
+
* It contains only version and timestamps — no sensitive data.
|
|
40
|
+
*/
|
|
41
|
+
export function createManifest(syncDir) {
|
|
42
|
+
const now = new Date().toISOString();
|
|
43
|
+
const manifest = {
|
|
44
|
+
version: VERSION,
|
|
45
|
+
lastSync: now,
|
|
46
|
+
files: {},
|
|
47
|
+
};
|
|
48
|
+
fs.writeFileSync(path.join(syncDir, STATE_FILES.MANIFEST), JSON.stringify(manifest, null, 2));
|
|
49
|
+
return manifest;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Execute the fresh init flow.
|
|
53
|
+
*
|
|
54
|
+
* 1. Generate Age key pair.
|
|
55
|
+
* 2. Save private key to config dir (0o600).
|
|
56
|
+
* 3. Display public key.
|
|
57
|
+
* 4. Handle backup (skip in --no-interactive).
|
|
58
|
+
* 5. Prompt for Git remote URL (or use --remote).
|
|
59
|
+
* 6. Validate remote URL.
|
|
60
|
+
* 7. Initialize sync Git repo.
|
|
61
|
+
* 8. Add remote.
|
|
62
|
+
* 9. Create manifest.json.
|
|
63
|
+
* 10. Commit and push.
|
|
64
|
+
*/
|
|
65
|
+
export async function executeInit(options) {
|
|
66
|
+
const configDir = getConfigDir();
|
|
67
|
+
const syncDir = getSyncDir();
|
|
68
|
+
// 1. Generate key pair
|
|
69
|
+
const { publicKey, privateKey } = await generateKey();
|
|
70
|
+
// 2. Save private key
|
|
71
|
+
saveKey(configDir, privateKey);
|
|
72
|
+
// 3-4. Display key info (handled by CLI output in registerInitCommand)
|
|
73
|
+
// 5-6. Remote URL
|
|
74
|
+
let remoteUrl;
|
|
75
|
+
if (options.remote) {
|
|
76
|
+
validateRemoteUrl(options.remote);
|
|
77
|
+
remoteUrl = options.remote;
|
|
78
|
+
}
|
|
79
|
+
// 7. Init sync repo
|
|
80
|
+
await initRepo(syncDir);
|
|
81
|
+
// 8. Add remote if provided
|
|
82
|
+
if (remoteUrl) {
|
|
83
|
+
await addRemote(syncDir, remoteUrl);
|
|
84
|
+
}
|
|
85
|
+
// 9. Create manifest
|
|
86
|
+
createManifest(syncDir);
|
|
87
|
+
// 10. Commit
|
|
88
|
+
await commitState(syncDir, [STATE_FILES.MANIFEST], 'chore: initialize context sync');
|
|
89
|
+
return {
|
|
90
|
+
publicKey,
|
|
91
|
+
configDir,
|
|
92
|
+
syncDir,
|
|
93
|
+
remoteUrl,
|
|
94
|
+
manifestCreated: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Execute the restore flow.
|
|
99
|
+
*
|
|
100
|
+
* 1. Accept private key (from --stdin or prompt).
|
|
101
|
+
* 2. Save key with 0o600 permissions.
|
|
102
|
+
* 3. Prompt for Git remote URL (or use --remote).
|
|
103
|
+
* 4. Validate remote URL.
|
|
104
|
+
* 5. Clone the sync repo to ~/.context-sync/.
|
|
105
|
+
* 6. Decrypt manifest, list found projects.
|
|
106
|
+
* 7. Print summary.
|
|
107
|
+
*/
|
|
108
|
+
export async function executeRestore(options) {
|
|
109
|
+
const configDir = getConfigDir();
|
|
110
|
+
const syncDir = getSyncDir();
|
|
111
|
+
// 1-2. Save provided key
|
|
112
|
+
if (!options.key) {
|
|
113
|
+
throw new Error('Private key is required for restore.\n' +
|
|
114
|
+
'Use --stdin to pipe the key, or run without --no-interactive for a prompt.');
|
|
115
|
+
}
|
|
116
|
+
// Validate key format
|
|
117
|
+
const trimmedKey = options.key.trim();
|
|
118
|
+
if (!trimmedKey.startsWith('AGE-SECRET-KEY-')) {
|
|
119
|
+
throw new Error('Invalid private key format. Expected key starting with AGE-SECRET-KEY-.\n' +
|
|
120
|
+
'Check your backup and try again.');
|
|
121
|
+
}
|
|
122
|
+
saveKey(configDir, trimmedKey);
|
|
123
|
+
// 3-4. Remote URL
|
|
124
|
+
let remoteUrl;
|
|
125
|
+
if (options.remote) {
|
|
126
|
+
validateRemoteUrl(options.remote);
|
|
127
|
+
remoteUrl = options.remote;
|
|
128
|
+
}
|
|
129
|
+
// 5. Clone or init repo
|
|
130
|
+
if (remoteUrl) {
|
|
131
|
+
// Clone via simple-git
|
|
132
|
+
const { simpleGit } = await import('simple-git');
|
|
133
|
+
const git = simpleGit();
|
|
134
|
+
await git.clone(remoteUrl, syncDir);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Just init locally (no remote to clone from)
|
|
138
|
+
await initRepo(syncDir);
|
|
139
|
+
}
|
|
140
|
+
// 6. Try to decrypt state and list projects
|
|
141
|
+
let projectCount = 0;
|
|
142
|
+
const projectNames = [];
|
|
143
|
+
const stateFile = path.join(syncDir, STATE_FILES.STATE);
|
|
144
|
+
if (fs.existsSync(stateFile)) {
|
|
145
|
+
try {
|
|
146
|
+
const privateKey = loadKey(configDir);
|
|
147
|
+
const ciphertext = fs.readFileSync(stateFile, 'utf-8');
|
|
148
|
+
const state = await decryptState(ciphertext, privateKey);
|
|
149
|
+
if (state.projects && Array.isArray(state.projects)) {
|
|
150
|
+
projectCount = state.projects.length;
|
|
151
|
+
for (const project of state.projects) {
|
|
152
|
+
if (project.name) {
|
|
153
|
+
projectNames.push(project.name);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Decryption failed — wrong key or no state yet
|
|
160
|
+
// This is not fatal for restore setup
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
configDir,
|
|
165
|
+
syncDir,
|
|
166
|
+
remoteUrl,
|
|
167
|
+
projectCount,
|
|
168
|
+
projectNames,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Read key from stdin (for piped input).
|
|
173
|
+
*/
|
|
174
|
+
export function readKeyFromStdin() {
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
let data = '';
|
|
177
|
+
if (process.stdin.isTTY) {
|
|
178
|
+
reject(new Error('No data piped to stdin. Use: echo "AGE-SECRET-KEY-..." | ctx-sync init --restore --stdin'));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
process.stdin.setEncoding('utf-8');
|
|
182
|
+
process.stdin.on('data', (chunk) => {
|
|
183
|
+
data += chunk;
|
|
184
|
+
});
|
|
185
|
+
process.stdin.on('end', () => {
|
|
186
|
+
resolve(data.trim());
|
|
187
|
+
});
|
|
188
|
+
process.stdin.on('error', reject);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Register the `init` command on the given Commander program.
|
|
193
|
+
*/
|
|
194
|
+
export function registerInitCommand(program) {
|
|
195
|
+
program
|
|
196
|
+
.command('init')
|
|
197
|
+
.description('Initialize ctx-sync (generate keys, set up sync repo)')
|
|
198
|
+
.option('--restore', 'Restore from backup on a new machine')
|
|
199
|
+
.option('--no-interactive', 'Skip interactive prompts (use defaults)')
|
|
200
|
+
.option('--skip-backup', 'Skip key backup prompt (not recommended)')
|
|
201
|
+
.option('--remote <url>', 'Git remote URL for syncing')
|
|
202
|
+
.option('--stdin', 'Read private key from stdin (for --restore)')
|
|
203
|
+
.action(withErrorHandler(async (opts) => {
|
|
204
|
+
const options = {
|
|
205
|
+
restore: opts['restore'],
|
|
206
|
+
noInteractive: opts['interactive'] === false,
|
|
207
|
+
skipBackup: opts['skipBackup'],
|
|
208
|
+
remote: opts['remote'],
|
|
209
|
+
stdin: opts['stdin'],
|
|
210
|
+
};
|
|
211
|
+
if (options.restore) {
|
|
212
|
+
// Restore flow
|
|
213
|
+
if (options.stdin) {
|
|
214
|
+
options.key = await readKeyFromStdin();
|
|
215
|
+
}
|
|
216
|
+
else if (!options.noInteractive) {
|
|
217
|
+
// Interactive prompt for key
|
|
218
|
+
const Enquirer = (await import('enquirer')).default;
|
|
219
|
+
const enquirer = new Enquirer();
|
|
220
|
+
const response = await enquirer.prompt({
|
|
221
|
+
type: 'password',
|
|
222
|
+
name: 'key',
|
|
223
|
+
message: 'Paste your private key (AGE-SECRET-KEY-...):',
|
|
224
|
+
});
|
|
225
|
+
options.key = response.key;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
throw new Error('Private key is required for restore.\n' +
|
|
229
|
+
'Use --stdin to pipe the key, or run without --no-interactive for a prompt.');
|
|
230
|
+
}
|
|
231
|
+
const result = await executeRestore(options);
|
|
232
|
+
const chalk = (await import('chalk')).default;
|
|
233
|
+
console.log(chalk.green('✅ Key restored') + ' (permissions set to 600)');
|
|
234
|
+
console.log(`📂 Sync directory: ${result.syncDir}`);
|
|
235
|
+
if (result.remoteUrl) {
|
|
236
|
+
console.log(chalk.green('✅ Remote configured:') + ` ${result.remoteUrl}`);
|
|
237
|
+
}
|
|
238
|
+
if (result.projectCount > 0) {
|
|
239
|
+
console.log(chalk.green(`✅ Found ${result.projectCount} projects:`));
|
|
240
|
+
for (const name of result.projectNames) {
|
|
241
|
+
console.log(` - ${name}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
console.log('No existing projects found (sync repo may be empty).');
|
|
246
|
+
}
|
|
247
|
+
console.log('\nAll state decrypted! 🎉');
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Fresh init flow
|
|
251
|
+
const result = await executeInit(options);
|
|
252
|
+
const chalk = (await import('chalk')).default;
|
|
253
|
+
console.log('\n🔐 Generating encryption key...');
|
|
254
|
+
console.log(chalk.green('✅ Public key: ') + result.publicKey);
|
|
255
|
+
console.log(chalk.green('✅ Private key saved to: ') +
|
|
256
|
+
path.join(result.configDir, 'key.txt'));
|
|
257
|
+
console.log(' Permissions: 600 (owner read/write only)');
|
|
258
|
+
if (!options.skipBackup && !options.noInteractive) {
|
|
259
|
+
console.log(chalk.yellow('\n⚠️ IMPORTANT: Back up your private key NOW!'));
|
|
260
|
+
console.log('Save it to 1Password, Bitwarden, or another password manager.');
|
|
261
|
+
}
|
|
262
|
+
if (result.remoteUrl) {
|
|
263
|
+
console.log(chalk.green('\n✅ Remote configured:') + ` ${result.remoteUrl}`);
|
|
264
|
+
}
|
|
265
|
+
console.log(chalk.green('\n✅ All set!'));
|
|
266
|
+
console.log('\nNow track your first project:');
|
|
267
|
+
console.log(' $ cd ~/projects/my-app');
|
|
268
|
+
console.log(' $ ctx-sync track');
|
|
269
|
+
}
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync key` command group.
|
|
3
|
+
*
|
|
4
|
+
* Manages encryption key lifecycle:
|
|
5
|
+
* - `key show` — display public key (NEVER the private key).
|
|
6
|
+
* - `key verify` — check key file and config directory permissions.
|
|
7
|
+
* - `key rotate` — generate new key, re-encrypt all state, rewrite Git history.
|
|
8
|
+
* - `key update` — restore a rotated key from another machine (stdin/prompt).
|
|
9
|
+
*
|
|
10
|
+
* **Security:**
|
|
11
|
+
* - `key show` never outputs the private key.
|
|
12
|
+
* - `key rotate` rewrites Git history so old encrypted blobs are purged.
|
|
13
|
+
* - `key update` reads the new private key from stdin, never from CLI args.
|
|
14
|
+
*
|
|
15
|
+
* @module commands/key
|
|
16
|
+
*/
|
|
17
|
+
import type { Command } from 'commander';
|
|
18
|
+
/** Result of key show */
|
|
19
|
+
export interface KeyShowResult {
|
|
20
|
+
publicKey: string;
|
|
21
|
+
}
|
|
22
|
+
/** Result of key verify */
|
|
23
|
+
export interface KeyVerifyResult {
|
|
24
|
+
valid: boolean;
|
|
25
|
+
keyFileExists: boolean;
|
|
26
|
+
keyFilePerms: number | null;
|
|
27
|
+
configDirPerms: number | null;
|
|
28
|
+
issues: string[];
|
|
29
|
+
}
|
|
30
|
+
/** Options for key rotate */
|
|
31
|
+
export interface KeyRotateOptions {
|
|
32
|
+
/** Skip interactive prompts (for testing / non-interactive mode) */
|
|
33
|
+
noInteractive?: boolean;
|
|
34
|
+
/** Skip force-push to remote (for testing / local-only setups) */
|
|
35
|
+
noForcePush?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Result of key rotate */
|
|
38
|
+
export interface KeyRotateResult {
|
|
39
|
+
oldPublicKey: string;
|
|
40
|
+
newPublicKey: string;
|
|
41
|
+
filesReEncrypted: string[];
|
|
42
|
+
gitHistoryRewritten: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Options for key update */
|
|
45
|
+
export interface KeyUpdateOptions {
|
|
46
|
+
/** Read key from stdin instead of interactive prompt */
|
|
47
|
+
stdin?: boolean;
|
|
48
|
+
/** Override key input for testing */
|
|
49
|
+
keyInput?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Result of key update */
|
|
52
|
+
export interface KeyUpdateResult {
|
|
53
|
+
publicKey: string;
|
|
54
|
+
configDir: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute `ctx-sync key show`.
|
|
58
|
+
*
|
|
59
|
+
* Loads the private key and derives the public key from it.
|
|
60
|
+
* NEVER outputs or returns the private key.
|
|
61
|
+
*/
|
|
62
|
+
export declare function executeKeyShow(): Promise<KeyShowResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Execute `ctx-sync key verify`.
|
|
65
|
+
*
|
|
66
|
+
* Checks that the key file and config directory have correct permissions.
|
|
67
|
+
*/
|
|
68
|
+
export declare function executeKeyVerify(): KeyVerifyResult;
|
|
69
|
+
/**
|
|
70
|
+
* Execute `ctx-sync key rotate`.
|
|
71
|
+
*
|
|
72
|
+
* 1. Generate a new key pair.
|
|
73
|
+
* 2. Decrypt ALL .age files with the old key.
|
|
74
|
+
* 3. Re-encrypt ALL with the new key.
|
|
75
|
+
* 4. Save the new private key (0o600).
|
|
76
|
+
* 5. Rewrite Git history to remove old encrypted blobs.
|
|
77
|
+
* 6. Optionally force-push to remote.
|
|
78
|
+
* 7. Return result for display.
|
|
79
|
+
*/
|
|
80
|
+
export declare function executeKeyRotate(_options?: KeyRotateOptions): Promise<KeyRotateResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Execute `ctx-sync key update`.
|
|
83
|
+
*
|
|
84
|
+
* Prompts for (or reads from stdin) a new private key, validates it,
|
|
85
|
+
* and saves it with correct permissions.
|
|
86
|
+
*/
|
|
87
|
+
export declare function executeKeyUpdate(options?: KeyUpdateOptions): Promise<KeyUpdateResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Register the `ctx-sync key` command group on the given program.
|
|
90
|
+
*/
|
|
91
|
+
export declare function registerKeyCommand(program: Command): void;
|
|
92
|
+
//# sourceMappingURL=key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/commands/key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBzC,yBAAyB;AACzB,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2BAA2B;AAC3B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,6BAA6B;AAC7B,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,2BAA2B;AAC3B,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED,6BAA6B;AAC7B,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2BAA2B;AAC3B,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;;GAKG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC,CAK7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,CAGlD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,GAAE,gBAAqB,GAC9B,OAAO,CAAC,eAAe,CAAC,CAuD1B;AAgDD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CA+B1B;AAuCD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwEzD"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ctx-sync key` command group.
|
|
3
|
+
*
|
|
4
|
+
* Manages encryption key lifecycle:
|
|
5
|
+
* - `key show` — display public key (NEVER the private key).
|
|
6
|
+
* - `key verify` — check key file and config directory permissions.
|
|
7
|
+
* - `key rotate` — generate new key, re-encrypt all state, rewrite Git history.
|
|
8
|
+
* - `key update` — restore a rotated key from another machine (stdin/prompt).
|
|
9
|
+
*
|
|
10
|
+
* **Security:**
|
|
11
|
+
* - `key show` never outputs the private key.
|
|
12
|
+
* - `key rotate` rewrites Git history so old encrypted blobs are purged.
|
|
13
|
+
* - `key update` reads the new private key from stdin, never from CLI args.
|
|
14
|
+
*
|
|
15
|
+
* @module commands/key
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'node:fs';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import * as readline from 'node:readline';
|
|
20
|
+
import { withErrorHandler } from '../utils/errors.js';
|
|
21
|
+
import { identityToRecipient } from 'age-encryption';
|
|
22
|
+
import { generateKey, decryptState, encryptState } from '../core/encryption.js';
|
|
23
|
+
import { saveKey, loadKey, verifyPermissions, KEY_FILE_PERMS, } from '../core/key-store.js';
|
|
24
|
+
import { listStateFiles, readManifest, writeManifest, } from '../core/state-manager.js';
|
|
25
|
+
import { getConfigDir, getSyncDir } from './init.js';
|
|
26
|
+
// ─── Core Logic ───────────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Execute `ctx-sync key show`.
|
|
29
|
+
*
|
|
30
|
+
* Loads the private key and derives the public key from it.
|
|
31
|
+
* NEVER outputs or returns the private key.
|
|
32
|
+
*/
|
|
33
|
+
export async function executeKeyShow() {
|
|
34
|
+
const configDir = getConfigDir();
|
|
35
|
+
const privateKey = loadKey(configDir);
|
|
36
|
+
const publicKey = await identityToRecipient(privateKey);
|
|
37
|
+
return { publicKey };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Execute `ctx-sync key verify`.
|
|
41
|
+
*
|
|
42
|
+
* Checks that the key file and config directory have correct permissions.
|
|
43
|
+
*/
|
|
44
|
+
export function executeKeyVerify() {
|
|
45
|
+
const configDir = getConfigDir();
|
|
46
|
+
return verifyPermissions(configDir);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Execute `ctx-sync key rotate`.
|
|
50
|
+
*
|
|
51
|
+
* 1. Generate a new key pair.
|
|
52
|
+
* 2. Decrypt ALL .age files with the old key.
|
|
53
|
+
* 3. Re-encrypt ALL with the new key.
|
|
54
|
+
* 4. Save the new private key (0o600).
|
|
55
|
+
* 5. Rewrite Git history to remove old encrypted blobs.
|
|
56
|
+
* 6. Optionally force-push to remote.
|
|
57
|
+
* 7. Return result for display.
|
|
58
|
+
*/
|
|
59
|
+
export async function executeKeyRotate(_options = {}) {
|
|
60
|
+
const configDir = getConfigDir();
|
|
61
|
+
const syncDir = getSyncDir();
|
|
62
|
+
// 1. Load old key
|
|
63
|
+
const oldPrivateKey = loadKey(configDir);
|
|
64
|
+
const oldPublicKey = await identityToRecipient(oldPrivateKey);
|
|
65
|
+
// 2. Generate new key pair
|
|
66
|
+
const { publicKey: newPublicKey, privateKey: newPrivateKey } = await generateKey();
|
|
67
|
+
// 3. Re-encrypt all .age files
|
|
68
|
+
const ageFiles = listStateFiles(syncDir);
|
|
69
|
+
const filesReEncrypted = [];
|
|
70
|
+
for (const filename of ageFiles) {
|
|
71
|
+
const filePath = path.join(syncDir, filename);
|
|
72
|
+
const ciphertext = fs.readFileSync(filePath, 'utf-8');
|
|
73
|
+
if (!ciphertext.trim()) {
|
|
74
|
+
continue; // Skip empty files
|
|
75
|
+
}
|
|
76
|
+
// Decrypt with old key, re-encrypt with new key
|
|
77
|
+
const plainData = await decryptState(ciphertext, oldPrivateKey);
|
|
78
|
+
const newCiphertext = await encryptState(plainData, newPublicKey);
|
|
79
|
+
fs.writeFileSync(filePath, newCiphertext, 'utf-8');
|
|
80
|
+
filesReEncrypted.push(filename);
|
|
81
|
+
}
|
|
82
|
+
// 4. Save the new private key
|
|
83
|
+
saveKey(configDir, newPrivateKey);
|
|
84
|
+
// 5. Update manifest
|
|
85
|
+
const manifest = readManifest(syncDir);
|
|
86
|
+
if (manifest) {
|
|
87
|
+
manifest.lastSync = new Date().toISOString();
|
|
88
|
+
writeManifest(syncDir, manifest);
|
|
89
|
+
}
|
|
90
|
+
// 6. Rewrite Git history to remove old encrypted blobs
|
|
91
|
+
let gitHistoryRewritten = false;
|
|
92
|
+
const gitDir = path.join(syncDir, '.git');
|
|
93
|
+
if (fs.existsSync(gitDir)) {
|
|
94
|
+
gitHistoryRewritten = await rewriteGitHistory(syncDir);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
oldPublicKey,
|
|
98
|
+
newPublicKey,
|
|
99
|
+
filesReEncrypted,
|
|
100
|
+
gitHistoryRewritten,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Rewrite Git history to remove old encrypted blobs.
|
|
105
|
+
*
|
|
106
|
+
* Uses `git checkout --orphan` + fresh commit to create a clean history
|
|
107
|
+
* with only the current (re-encrypted) files. This is safer and more
|
|
108
|
+
* portable than `git filter-branch`.
|
|
109
|
+
*/
|
|
110
|
+
async function rewriteGitHistory(syncDir) {
|
|
111
|
+
const { simpleGit } = await import('simple-git');
|
|
112
|
+
const git = simpleGit(syncDir);
|
|
113
|
+
try {
|
|
114
|
+
// Create an orphan branch with only current files
|
|
115
|
+
const orphanBranch = `_key-rotation-${Date.now()}`;
|
|
116
|
+
await git.checkout(['--orphan', orphanBranch]);
|
|
117
|
+
// Stage all current files
|
|
118
|
+
await git.add('.');
|
|
119
|
+
// Commit
|
|
120
|
+
await git.commit('key: rotate — re-encrypted all state with new key');
|
|
121
|
+
// Delete old main/master branch, rename orphan
|
|
122
|
+
const branches = await git.branchLocal();
|
|
123
|
+
const mainBranch = branches.all.find((b) => b === 'main' || b === 'master') ?? 'main';
|
|
124
|
+
// Only delete the old branch if it's different from our orphan
|
|
125
|
+
if (branches.all.includes(mainBranch) && mainBranch !== orphanBranch) {
|
|
126
|
+
await git.branch(['-D', mainBranch]);
|
|
127
|
+
}
|
|
128
|
+
await git.branch(['-m', mainBranch]);
|
|
129
|
+
// Clean up old objects
|
|
130
|
+
await git.raw(['reflog', 'expire', '--expire=now', '--all']);
|
|
131
|
+
await git.raw(['gc', '--prune=now', '--aggressive']);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// If history rewrite fails, the rotation still succeeded
|
|
136
|
+
// (files are re-encrypted), just old history remains
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Execute `ctx-sync key update`.
|
|
142
|
+
*
|
|
143
|
+
* Prompts for (or reads from stdin) a new private key, validates it,
|
|
144
|
+
* and saves it with correct permissions.
|
|
145
|
+
*/
|
|
146
|
+
export async function executeKeyUpdate(options = {}) {
|
|
147
|
+
const configDir = getConfigDir();
|
|
148
|
+
let keyInput;
|
|
149
|
+
if (options.keyInput !== undefined) {
|
|
150
|
+
// Direct input (for testing)
|
|
151
|
+
keyInput = options.keyInput;
|
|
152
|
+
}
|
|
153
|
+
else if (options.stdin) {
|
|
154
|
+
// Read from stdin
|
|
155
|
+
keyInput = await readKeyFromStdin();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Interactive prompt
|
|
159
|
+
keyInput = await readKeyFromPrompt();
|
|
160
|
+
}
|
|
161
|
+
const trimmedKey = keyInput.trim();
|
|
162
|
+
// Validate the key looks like an Age private key
|
|
163
|
+
if (!trimmedKey.startsWith('AGE-SECRET-KEY-')) {
|
|
164
|
+
throw new Error('Invalid key format. Expected an Age private key starting with AGE-SECRET-KEY-');
|
|
165
|
+
}
|
|
166
|
+
// Derive public key to verify it's valid
|
|
167
|
+
const publicKey = await identityToRecipient(trimmedKey);
|
|
168
|
+
// Save with secure permissions
|
|
169
|
+
saveKey(configDir, trimmedKey);
|
|
170
|
+
return { publicKey, configDir };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Read a private key from stdin (pipe mode).
|
|
174
|
+
*/
|
|
175
|
+
function readKeyFromStdin() {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
let data = '';
|
|
178
|
+
process.stdin.setEncoding('utf-8');
|
|
179
|
+
process.stdin.on('data', (chunk) => {
|
|
180
|
+
data += chunk;
|
|
181
|
+
});
|
|
182
|
+
process.stdin.on('end', () => resolve(data));
|
|
183
|
+
process.stdin.on('error', reject);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Read a private key from an interactive prompt.
|
|
188
|
+
*/
|
|
189
|
+
function readKeyFromPrompt() {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
const rl = readline.createInterface({
|
|
192
|
+
input: process.stdin,
|
|
193
|
+
output: process.stdout,
|
|
194
|
+
});
|
|
195
|
+
rl.question('Paste your private key (AGE-SECRET-KEY-...): ', (answer) => {
|
|
196
|
+
rl.close();
|
|
197
|
+
if (!answer) {
|
|
198
|
+
reject(new Error('No key provided.'));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
resolve(answer);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// ─── Commander Registration ───────────────────────────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Register the `ctx-sync key` command group on the given program.
|
|
209
|
+
*/
|
|
210
|
+
export function registerKeyCommand(program) {
|
|
211
|
+
const keyCmd = program
|
|
212
|
+
.command('key')
|
|
213
|
+
.description('Manage encryption keys');
|
|
214
|
+
// ── key show ──────────────────────────────────────────────────────
|
|
215
|
+
keyCmd
|
|
216
|
+
.command('show')
|
|
217
|
+
.description('Display your public key (never shows private key)')
|
|
218
|
+
.action(withErrorHandler(async () => {
|
|
219
|
+
const result = await executeKeyShow();
|
|
220
|
+
console.log(`Public key: ${result.publicKey}`);
|
|
221
|
+
}));
|
|
222
|
+
// ── key verify ────────────────────────────────────────────────────
|
|
223
|
+
keyCmd
|
|
224
|
+
.command('verify')
|
|
225
|
+
.description('Verify key file and config directory permissions')
|
|
226
|
+
.action(withErrorHandler(async () => {
|
|
227
|
+
const result = executeKeyVerify();
|
|
228
|
+
if (result.valid) {
|
|
229
|
+
console.log('✓ Key verification passed');
|
|
230
|
+
console.log(` Key file: permissions ${result.keyFilePerms?.toString(8) ?? 'n/a'}`);
|
|
231
|
+
console.log(` Config dir: permissions ${result.configDirPerms?.toString(8) ?? 'n/a'}`);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
console.error('✗ Key verification failed:');
|
|
235
|
+
for (const issue of result.issues) {
|
|
236
|
+
console.error(` - ${issue}`);
|
|
237
|
+
}
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
}));
|
|
241
|
+
// ── key rotate ────────────────────────────────────────────────────
|
|
242
|
+
keyCmd
|
|
243
|
+
.command('rotate')
|
|
244
|
+
.description('Rotate encryption key — re-encrypts all state')
|
|
245
|
+
.option('-n, --no-interactive', 'Skip confirmation prompts')
|
|
246
|
+
.action(withErrorHandler(async (opts) => {
|
|
247
|
+
const result = await executeKeyRotate({
|
|
248
|
+
noInteractive: !opts.interactive,
|
|
249
|
+
});
|
|
250
|
+
console.log('✓ Key rotation complete');
|
|
251
|
+
console.log(` Old public key: ${result.oldPublicKey}`);
|
|
252
|
+
console.log(` New public key: ${result.newPublicKey}`);
|
|
253
|
+
console.log(` Files re-encrypted: ${String(result.filesReEncrypted.length)}`);
|
|
254
|
+
if (result.gitHistoryRewritten) {
|
|
255
|
+
console.log(' Git history: rewritten (old blobs purged)');
|
|
256
|
+
}
|
|
257
|
+
console.log('\n⚠ IMPORTANT: All other machines must run:\n' +
|
|
258
|
+
' ctx-sync key update\n' +
|
|
259
|
+
' Then paste the new private key.');
|
|
260
|
+
}));
|
|
261
|
+
// ── key update ────────────────────────────────────────────────────
|
|
262
|
+
keyCmd
|
|
263
|
+
.command('update')
|
|
264
|
+
.description('Update private key on this machine (after rotation elsewhere)')
|
|
265
|
+
.option('--stdin', 'Read key from stdin')
|
|
266
|
+
.action(withErrorHandler(async (opts) => {
|
|
267
|
+
const result = await executeKeyUpdate({ stdin: opts.stdin });
|
|
268
|
+
console.log('✓ Key updated');
|
|
269
|
+
console.log(` Public key: ${result.publicKey}`);
|
|
270
|
+
console.log(` Saved to: ${result.configDir}`);
|
|
271
|
+
console.log(` Permissions: ${KEY_FILE_PERMS.toString(8)}`);
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.js","sourceRoot":"","sources":["../../src/commands/key.ts"],"names":[],"mappings":"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"}
|