@@ -11,11 +11,24 @@ use {
1111 Result ,
1212 } ,
1313 args:: ManualFormat ,
14+ manifest:: {
15+ Content ,
16+ ContentWrapper ,
17+ EncodedValue ,
18+ EncodedValueWrapper ,
19+ Manifest ,
20+ ManifestEnv ,
21+ ManifestProfile ,
22+ Secret ,
23+ SecretAllocation ,
24+ SecretAllocationWrapper ,
25+ SecretWrapper ,
26+ } ,
1427 regex:: Regex ,
1528 std:: {
1629 collections:: HashMap ,
17- process:: Command ,
1830 path:: Path ,
31+ process:: Command ,
1932 } ,
2033} ;
2134
@@ -43,7 +56,12 @@ async fn main() -> Result<()> {
4356 crate :: reference:: build_shell_completion ( & path, & shell) ?;
4457 Ok ( ( ) )
4558 } ,
46- | crate :: args:: Command :: Unlock { manifest, profile_name, command, force } => {
59+ | crate :: args:: Command :: Unlock {
60+ manifest,
61+ profile_name,
62+ command,
63+ force,
64+ } => {
4765 let mut env_vars = HashMap :: new ( ) ;
4866 let mut pgp_manager = crate :: pgp:: PgpManager :: new ( ) . context ( "Failed to initialize PGP manager" ) ?;
4967
@@ -63,8 +81,6 @@ async fn main() -> Result<()> {
6381 } ,
6482 }
6583 }
66-
67- // Prepare files from manifest before running command
6884 let mut created_files: Vec < String > = Vec :: new ( ) ;
6985 for ( file_path, content) in profile. files . iter ( ) {
7086 let absolute_path = Path :: new ( file_path) ;
@@ -74,7 +90,6 @@ async fn main() -> Result<()> {
7490 absolute_path. display( )
7591 ) ) ;
7692 }
77-
7893 let value = content. inner . get_value ( & mut pgp_manager) ?;
7994 if let Some ( parent) = absolute_path. parent ( ) {
8095 std:: fs:: create_dir_all ( parent)
@@ -87,7 +102,7 @@ async fn main() -> Result<()> {
87102
88103 let exec_status: Result < Option < std:: process:: ExitStatus > > = match command {
89104 | Some ( cmd_args) if !cmd_args. is_empty ( ) => {
90- let status = execute_command_with_env ( & cmd_args, & env_vars, & profile. env . keep ) ?;
105+ let status = exec_command ( & cmd_args, & env_vars, & profile. env . keep ) ?;
91106 Ok ( Some ( status) )
92107 } ,
93108 | _ => {
@@ -98,15 +113,10 @@ async fn main() -> Result<()> {
98113 } ,
99114 } ;
100115
101- // Cleanup files after command execution or printing envs
102116 let mut cleanup_error: Option < anyhow:: Error > = None ;
103117 for file in created_files. iter ( ) {
104118 if let Err ( e) = std:: fs:: remove_file ( file) {
105- cleanup_error = Some ( anyhow:: anyhow!(
106- "Failed to remove file '{}': {}" ,
107- file,
108- e
109- ) ) ;
119+ cleanup_error = Some ( anyhow:: anyhow!( "Failed to remove file '{}': {}" , file, e) ) ;
110120 }
111121 }
112122
@@ -136,60 +146,110 @@ async fn main() -> Result<()> {
136146 ) ) ;
137147 }
138148
139- let example_config = r#"
140- version = "0.1.0"
141-
142- profiles = {
143- default = {
144- env = {
145- # keep = ["^PATH$", "^LC_.*"] # Uncomment to only preserve matching host env vars
146- vars = {
147- # Example: plain literal value
148- APP_NAME.plain.literal = "myapp"
149-
150- # Example: plain base64-encoded value
151- # DB_HOST.plain.base64 = "bG9jYWxob3N0" # "localhost" in base64
152-
153- # Example: secure value using PGP secret from file
154- # SECRET_TOKEN.secure {
155- # secret.pgp.file = "/path/to/private.key"
156- # value.literal = "-----BEGIN PGP MESSAGE-----..."
157- # }
158-
159- # Example: secure value using PGP secret from GCP (fully qualified resource)
160- # API_KEY.secure {
161- # secret.pgp.gcp.secret = "projects/myproject/secrets/my-pgp-key"
162- # # secret.pgp.gcp.version = "latest" # optional
163- # value.base64 = "<base64-encoded-ASCII-armored-message>"
164- # }
165-
166- # Example: secure value using inline private key
167- # ENCRYPTED_TOKEN.secure {
168- # secret.pgp.literal.literal = """
169- # -----BEGIN PGP PRIVATE KEY BLOCK-----
170- # ...
171- # -----END PGP PRIVATE KEY BLOCK-----
172- # """
173- # value.literal = "-----BEGIN PGP MESSAGE-----..."
174- # }
175- }
176- }
177- }
178- }
179- "# ;
149+ let mut profiles = HashMap :: new ( ) ;
150+ let mut vars = HashMap :: new ( ) ;
151+
152+ vars. insert ( "APP_NAME" . to_string ( ) , ContentWrapper {
153+ inner : Content :: Plain ( EncodedValue :: Literal ( "myapp" . to_string ( ) ) ) ,
154+ } ) ;
155+
156+ vars. insert ( "DB_HOST_EXAMPLE" . to_string ( ) , ContentWrapper {
157+ inner : Content :: Plain ( EncodedValue :: Base64 ( "bG9jYWxob3N0" . to_string ( ) ) ) ,
158+ } ) ;
159+
160+ vars. insert ( "SECRET_TOKEN_EXAMPLE" . to_string ( ) , ContentWrapper {
161+ inner : Content :: Secure {
162+ secret : SecretWrapper {
163+ inner : Secret :: PGP ( SecretAllocationWrapper {
164+ inner : SecretAllocation :: File ( "/path/to/private.key" . to_string ( ) ) ,
165+ } ) ,
166+ } ,
167+ value : EncodedValueWrapper {
168+ inner : EncodedValue :: Literal ( "-----BEGIN PGP MESSAGE-----..." . to_string ( ) ) ,
169+ } ,
170+ } ,
171+ } ) ;
172+
173+ vars. insert ( "API_KEY_EXAMPLE" . to_string ( ) , ContentWrapper {
174+ inner : Content :: Secure {
175+ secret : SecretWrapper {
176+ inner : Secret :: PGP ( SecretAllocationWrapper {
177+ inner : SecretAllocation :: Gcp {
178+ secret : "projects/myproject/secrets/my-pgp-key" . to_string ( ) ,
179+ version : Some ( "latest" . to_string ( ) ) ,
180+ } ,
181+ } ) ,
182+ } ,
183+ value : EncodedValueWrapper {
184+ inner : EncodedValue :: Base64 ( "<base64-encoded-ASCII-armored-message>" . to_string ( ) ) ,
185+ } ,
186+ } ,
187+ } ) ;
188+
189+ vars. insert ( "GPG_ENCRYPTED_EXAMPLE" . to_string ( ) , ContentWrapper {
190+ inner : Content :: Secure {
191+ secret : SecretWrapper {
192+ inner : Secret :: PGP ( SecretAllocationWrapper {
193+ inner : SecretAllocation :: Gpg {
194+ fingerprint : "1E1BAC706C352094D490D5393F5167F1F3002043" . to_string ( ) ,
195+ } ,
196+ } ) ,
197+ } ,
198+ value : EncodedValueWrapper {
199+ inner : EncodedValue :: Base64 ( "<base64-encoded-ASCII-armored-message>" . to_string ( ) ) ,
200+ } ,
201+ } ,
202+ } ) ;
203+
204+ let mut files = HashMap :: new ( ) ;
205+
206+ files. insert ( "./config.json" . to_string ( ) , ContentWrapper {
207+ inner : Content :: Plain ( EncodedValue :: Literal ( "{\" key\" : \" value\" }" . to_string ( ) ) ) ,
208+ } ) ;
209+
210+ files. insert ( "./credentials.key" . to_string ( ) , ContentWrapper {
211+ inner : Content :: Secure {
212+ secret : SecretWrapper {
213+ inner : Secret :: PGP ( SecretAllocationWrapper {
214+ inner : SecretAllocation :: File ( "/path/to/private.key" . to_string ( ) ) ,
215+ } ) ,
216+ } ,
217+ value : EncodedValueWrapper {
218+ inner : EncodedValue :: Literal ( "-----BEGIN PGP MESSAGE-----..." . to_string ( ) ) ,
219+ } ,
220+ } ,
221+ } ) ;
222+
223+ let default_profile = ManifestProfile {
224+ files,
225+ env : ManifestEnv {
226+ keep : Some ( vec ! [ "^PATH$" . to_string( ) , "^LC_.*" . to_string( ) ] ) ,
227+ vars,
228+ } ,
229+ } ;
230+
231+ profiles. insert ( "default" . to_string ( ) , default_profile) ;
232+
233+ let manifest = Manifest {
234+ version : env ! ( "CARGO_PKG_VERSION" ) . to_string ( ) ,
235+ profiles,
236+ } ;
237+
238+ let json_config =
239+ serde_json:: to_string_pretty ( & manifest) . context ( "Failed to serialize example config to JSON" ) ?;
180240
181- std:: fs:: write ( & path, example_config )
241+ std:: fs:: write ( & path, json_config )
182242 . with_context ( || format ! ( "Failed to write config file: {}" , path. display( ) ) ) ?;
183243
184244 println ! ( "Created example configuration file: {}" , path. display( ) ) ;
185245 println ! ( "Edit the file to add your own variables and PGP keys." ) ;
246+ println ! ( "Note: Remove '_EXAMPLE' suffix from variable names before using them." ) ;
186247 Ok ( ( ) )
187248 } ,
188249 }
189250}
190251
191- /// Execute a command with the specified environment variables
192- fn execute_command_with_env (
252+ fn exec_command (
193253 cmd_args : & [ String ] ,
194254 env_vars : & HashMap < String , String > ,
195255 keep_env_vars : & Option < Vec < String > > ,
0 commit comments