1+ <?php
2+
3+ namespace Neuron \Cli \Commands \Secrets \Key ;
4+
5+ use Neuron \Cli \Commands \Command ;
6+ use Neuron \Data \Settings \SecretManager ;
7+
8+ /**
9+ * Generate encryption key command
10+ *
11+ * Generates a new encryption key for securing credentials.
12+ * Keys are cryptographically secure random values.
13+ *
14+ * Usage:
15+ * neuron secrets:key:generate # Generate master key
16+ * neuron secrets:key:generate --env=production # Generate production key
17+ * neuron secrets:key:generate --force # Overwrite existing key
18+ *
19+ * @package Neuron\Cli\Commands\Secrets\Key
20+ */
21+ class GenerateCommand extends Command
22+ {
23+ private SecretManager $ secretManager ;
24+
25+ /**
26+ * @inheritDoc
27+ */
28+ public function getName (): string
29+ {
30+ return 'secrets:key:generate ' ;
31+ }
32+
33+ /**
34+ * @inheritDoc
35+ */
36+ public function getDescription (): string
37+ {
38+ return 'Generate a new encryption key for secrets ' ;
39+ }
40+
41+ /**
42+ * @inheritDoc
43+ */
44+ public function configure (): void
45+ {
46+ $ this ->addOption ( 'env ' , 'e ' , true , 'Environment for the key (default: master key) ' );
47+ $ this ->addOption ( 'config ' , 'c ' , true , 'Config directory path (default: config) ' );
48+ $ this ->addOption ( 'force ' , 'f ' , false , 'Overwrite existing key file ' );
49+ $ this ->addOption ( 'show ' , 's ' , false , 'Display the generated key ' );
50+ $ this ->addOption ( 'verbose ' , 'v ' , false , 'Verbose output ' );
51+ }
52+
53+ /**
54+ * @inheritDoc
55+ */
56+ public function execute (): int
57+ {
58+ $ configPath = $ this ->input ->getOption ( 'config ' , 'config ' );
59+ $ env = $ this ->input ->getOption ( 'env ' );
60+ $ force = $ this ->input ->hasOption ( 'force ' );
61+ $ show = $ this ->input ->hasOption ( 'show ' );
62+
63+ // Determine key path based on environment
64+ if ( $ env )
65+ {
66+ $ keyPath = $ configPath . '/secrets/ ' . $ env . '.key ' ;
67+ $ keyName = $ env . ' environment key ' ;
68+
69+ // Ensure directory exists
70+ $ dir = dirname ( $ keyPath );
71+ if ( !is_dir ( $ dir ) )
72+ {
73+ if ( !mkdir ( $ dir , 0755 , true ) )
74+ {
75+ $ this ->output ->error ( "Failed to create directory: {$ dir }" );
76+ return 1 ;
77+ }
78+ }
79+ }
80+ else
81+ {
82+ $ keyPath = $ configPath . '/master.key ' ;
83+ $ keyName = 'master key ' ;
84+
85+ // Ensure directory exists
86+ $ dir = dirname ( $ keyPath );
87+ if ( !is_dir ( $ dir ) )
88+ {
89+ if ( !mkdir ( $ dir , 0755 , true ) )
90+ {
91+ $ this ->output ->error ( "Failed to create directory: {$ dir }" );
92+ return 1 ;
93+ }
94+ }
95+ }
96+
97+ // Check if key already exists
98+ if ( file_exists ( $ keyPath ) && !$ force )
99+ {
100+ $ this ->output ->error ( "Key file already exists: {$ keyPath }" );
101+ $ this ->output ->info ( "Use --force to overwrite the existing key. " );
102+ $ this ->output ->warning ( "WARNING: Overwriting will make existing encrypted files unreadable! " );
103+ return 1 ;
104+ }
105+
106+ // Warn about overwriting
107+ if ( file_exists ( $ keyPath ) && $ force )
108+ {
109+ $ this ->output ->warning ( "You are about to overwrite an existing key! " );
110+ $ this ->output ->warning ( "This will make any files encrypted with the old key unreadable. " );
111+
112+ if ( !$ this ->confirm ( "Are you absolutely sure you want to continue? " ) )
113+ {
114+ $ this ->output ->info ( "Operation cancelled. " );
115+ return 0 ;
116+ }
117+ }
118+
119+ // Create SecretManager and generate key
120+ $ this ->secretManager = new SecretManager ();
121+
122+ try
123+ {
124+ $ key = $ this ->secretManager ->generateKey ( $ keyPath , $ force );
125+
126+ $ this ->output ->success ( "Generated {$ keyName } at: {$ keyPath }" );
127+
128+ // Show the key if requested
129+ if ( $ show )
130+ {
131+ $ this ->output ->newLine ();
132+ $ this ->output ->section ( "Generated Key " );
133+ $ this ->output ->write ( $ key );
134+ $ this ->output ->newLine ();
135+ $ this ->output ->warning ( "This key is shown only once. Store it securely! " );
136+ }
137+
138+ // Display instructions
139+ $ this ->output ->newLine ();
140+ $ this ->output ->info ( "Next steps: " );
141+ $ this ->output ->write ( "1. Add {$ keyPath } to .gitignore (NEVER commit this file) " );
142+ $ this ->output ->write ( "2. Share this key securely with your team " );
143+ $ this ->output ->write ( "3. Use 'neuron secrets:edit " . ($ env ? " --env= {$ env }" : "" ) . "' to add secrets " );
144+
145+ // Environment variable alternative
146+ $ envVar = 'NEURON_ ' . strtoupper (
147+ str_replace ( ['/ ' , '. ' , '- ' ], '_ ' , basename ( $ keyPath , '.key ' ) )
148+ ) . '_KEY ' ;
149+ $ this ->output ->newLine ();
150+ $ this ->output ->info ( "Alternative: Set the key as an environment variable: " );
151+ if ( $ show )
152+ {
153+ $ this ->output ->write ( "export {$ envVar }= {$ key }" );
154+ }
155+ else
156+ {
157+ $ this ->output ->write ( "export {$ envVar }=<KEY_FROM_ {$ keyPath }> " );
158+ }
159+ }
160+ catch ( \Exception $ e )
161+ {
162+ $ this ->output ->error ( "Error generating key: " . $ e ->getMessage () );
163+
164+ if ( $ this ->input ->hasOption ( 'verbose ' ) )
165+ {
166+ $ this ->output ->write ( $ e ->getTraceAsString () );
167+ }
168+
169+ return 1 ;
170+ }
171+
172+ return 0 ;
173+ }
174+ }
0 commit comments