@@ -41,6 +41,8 @@ pub enum PushProfileError {
41
41
Copy ( std:: io:: Error ) ,
42
42
#[ error( "Nix copy command resulted in a bad exit code: {0:?}" ) ]
43
43
CopyExit ( Option < i32 > ) ,
44
+ #[ error( "The remote building option is not supported when using legacy nix" ) ]
45
+ RemoteBuildWithLegacyNix ,
44
46
}
45
47
46
48
pub struct PushProfileData < ' a > {
@@ -54,40 +56,7 @@ pub struct PushProfileData<'a> {
54
56
pub extra_build_args : & ' a [ String ] ,
55
57
}
56
58
57
- pub async fn push_profile ( data : PushProfileData < ' _ > ) -> Result < ( ) , PushProfileError > {
58
- debug ! (
59
- "Finding the deriver of store path for {}" ,
60
- & data. deploy_data. profile. profile_settings. path
61
- ) ;
62
-
63
- // `nix-store --query --deriver` doesn't work on invalid paths, so we parse output of show-derivation :(
64
- let mut show_derivation_command = Command :: new ( "nix" ) ;
65
-
66
- show_derivation_command
67
- . arg ( "show-derivation" )
68
- . arg ( & data. deploy_data . profile . profile_settings . path ) ;
69
-
70
- let show_derivation_output = show_derivation_command
71
- . output ( )
72
- . await
73
- . map_err ( PushProfileError :: ShowDerivation ) ?;
74
-
75
- match show_derivation_output. status . code ( ) {
76
- Some ( 0 ) => ( ) ,
77
- a => return Err ( PushProfileError :: ShowDerivationExit ( a) ) ,
78
- } ;
79
-
80
- let derivation_info: HashMap < & str , serde_json:: value:: Value > = serde_json:: from_str (
81
- std:: str:: from_utf8 ( & show_derivation_output. stdout )
82
- . map_err ( PushProfileError :: ShowDerivationUtf8 ) ?,
83
- )
84
- . map_err ( PushProfileError :: ShowDerivationParse ) ?;
85
-
86
- let derivation_name = derivation_info
87
- . keys ( )
88
- . next ( )
89
- . ok_or ( PushProfileError :: ShowDerivationEmpty ) ?;
90
-
59
+ pub async fn build_profile_locally ( data : & PushProfileData < ' _ > , derivation_name : & str ) -> Result < ( ) , PushProfileError > {
91
60
info ! (
92
61
"Building profile `{}` for node `{}`" ,
93
62
data. deploy_data. profile_name, data. deploy_data. node_name
@@ -118,9 +87,7 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr
118
87
( false , true ) => build_command. arg ( "--no-link" ) ,
119
88
} ;
120
89
121
- for extra_arg in data. extra_build_args {
122
- build_command. arg ( extra_arg) ;
123
- }
90
+ build_command. args ( data. extra_build_args ) ;
124
91
125
92
let build_exit_status = build_command
126
93
// Logging should be in stderr, this just stops the store path from printing for no reason
@@ -179,22 +146,77 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr
179
146
a => return Err ( PushProfileError :: SignExit ( a) ) ,
180
147
} ;
181
148
}
149
+ Ok ( ( ) )
150
+ }
182
151
152
+ pub async fn build_profile_remotely ( data : & PushProfileData < ' _ > , derivation_name : & str ) -> Result < ( ) , PushProfileError > {
183
153
info ! (
184
- "Copying profile `{}` to node `{}`" ,
154
+ "Building profile `{}` for node `{}` on remote host " ,
185
155
data. deploy_data. profile_name, data. deploy_data. node_name
186
156
) ;
187
157
188
- let mut copy_command = Command :: new ( "nix" ) ;
189
- copy_command. arg ( "copy" ) ;
158
+ let store_address = format ! ( "ssh-ng://{}@{}" ,
159
+ if data. deploy_data. profile. generic_settings. ssh_user. is_some( ) {
160
+ & data. deploy_data. profile. generic_settings. ssh_user. as_ref( ) . unwrap( )
161
+ } else {
162
+ & data. deploy_defs. ssh_user
163
+ } ,
164
+ data. deploy_data. node. node_settings. hostname
165
+ ) ;
190
166
191
- if data. deploy_data . merged_settings . fast_connection != Some ( true ) {
192
- copy_command. arg ( "--substitute-on-destination" ) ;
193
- }
167
+ let ssh_opts_str = data. deploy_data . merged_settings . ssh_opts . join ( " " ) ;
194
168
195
- if !data. check_sigs {
196
- copy_command. arg ( "--no-check-sigs" ) ;
197
- }
169
+
170
+ // copy the derivation to remote host so it can be built there
171
+ let copy_command_status = Command :: new ( "nix" ) . arg ( "copy" )
172
+ . arg ( "-s" ) // fetch dependencies from substitures, not localhost
173
+ . arg ( "--to" ) . arg ( & store_address)
174
+ . arg ( "--derivation" ) . arg ( derivation_name)
175
+ . env ( "NIX_SSHOPTS" , ssh_opts_str. clone ( ) )
176
+ . stdout ( Stdio :: null ( ) )
177
+ . status ( )
178
+ . await
179
+ . map_err ( PushProfileError :: Copy ) ?;
180
+
181
+ match copy_command_status. code ( ) {
182
+ Some ( 0 ) => ( ) ,
183
+ a => return Err ( PushProfileError :: CopyExit ( a) ) ,
184
+ } ;
185
+
186
+ let mut build_command = Command :: new ( "nix" ) ;
187
+ build_command
188
+ . arg ( "build" ) . arg ( derivation_name)
189
+ . arg ( "--eval-store" ) . arg ( "auto" )
190
+ . arg ( "--store" ) . arg ( & store_address)
191
+ . args ( data. extra_build_args )
192
+ . env ( "NIX_SSHOPTS" , ssh_opts_str. clone ( ) ) ;
193
+
194
+ debug ! ( "build command: {:?}" , build_command) ;
195
+
196
+ let build_exit_status = build_command
197
+ // Logging should be in stderr, this just stops the store path from printing for no reason
198
+ . stdout ( Stdio :: null ( ) )
199
+ . status ( )
200
+ . await
201
+ . map_err ( PushProfileError :: Build ) ?;
202
+
203
+ match build_exit_status. code ( ) {
204
+ Some ( 0 ) => ( ) ,
205
+ a => return Err ( PushProfileError :: BuildExit ( a) ) ,
206
+ } ;
207
+
208
+
209
+ Ok ( ( ) )
210
+ }
211
+
212
+ pub async fn push_profile ( data : PushProfileData < ' _ > ) -> Result < ( ) , PushProfileError > {
213
+ debug ! (
214
+ "Finding the deriver of store path for {}" ,
215
+ & data. deploy_data. profile. profile_settings. path
216
+ ) ;
217
+
218
+ // `nix-store --query --deriver` doesn't work on invalid paths, so we parse output of show-derivation :(
219
+ let mut show_derivation_command = Command :: new ( "nix" ) ;
198
220
199
221
let ssh_opts_str = data
200
222
. deploy_data
@@ -206,24 +228,78 @@ pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileEr
206
228
// .collect::<Vec<String>>()
207
229
. join ( " " ) ;
208
230
209
- let hostname = match data. deploy_data . cmd_overrides . hostname {
210
- Some ( ref x) => x,
211
- None => & data. deploy_data . node . node_settings . hostname ,
212
- } ;
213
231
214
- let copy_exit_status = copy_command
215
- . arg ( "--to " )
216
- . arg ( format ! ( "ssh://{}@{}" , data. deploy_defs . ssh_user , hostname ) )
217
- . arg ( & data . deploy_data . profile . profile_settings . path )
218
- . env ( "NIX_SSHOPTS" , ssh_opts_str )
219
- . status ( )
232
+ show_derivation_command
233
+ . arg ( "show-derivation " )
234
+ . arg ( & data. deploy_data . profile . profile_settings . path ) ;
235
+
236
+ let show_derivation_output = show_derivation_command
237
+ . output ( )
220
238
. await
221
- . map_err ( PushProfileError :: Copy ) ?;
239
+ . map_err ( PushProfileError :: ShowDerivation ) ?;
222
240
223
- match copy_exit_status . code ( ) {
241
+ match show_derivation_output . status . code ( ) {
224
242
Some ( 0 ) => ( ) ,
225
- a => return Err ( PushProfileError :: CopyExit ( a) ) ,
243
+ a => return Err ( PushProfileError :: ShowDerivationExit ( a) ) ,
226
244
} ;
227
245
246
+ let derivation_info: HashMap < & str , serde_json:: value:: Value > = serde_json:: from_str (
247
+ std:: str:: from_utf8 ( & show_derivation_output. stdout )
248
+ . map_err ( PushProfileError :: ShowDerivationUtf8 ) ?,
249
+ )
250
+ . map_err ( PushProfileError :: ShowDerivationParse ) ?;
251
+
252
+ let derivation_name = derivation_info
253
+ . keys ( )
254
+ . next ( )
255
+ . ok_or ( PushProfileError :: ShowDerivationEmpty ) ?;
256
+
257
+ if data. deploy_data . merged_settings . remote_build . unwrap_or ( false ) {
258
+ if !data. supports_flakes {
259
+ return Err ( PushProfileError :: RemoteBuildWithLegacyNix )
260
+ }
261
+
262
+ // remote building guarantees that the resulting derivation is stored on the target system
263
+ // no need to copy after building
264
+ build_profile_remotely ( & data, derivation_name) . await ?;
265
+ } else {
266
+ build_profile_locally ( & data, derivation_name) . await ?;
267
+
268
+ info ! (
269
+ "Copying profile `{}` to node `{}`" ,
270
+ data. deploy_data. profile_name, data. deploy_data. node_name
271
+ ) ;
272
+
273
+ let mut copy_command = Command :: new ( "nix" ) ;
274
+ copy_command. arg ( "copy" ) ;
275
+
276
+ if data. deploy_data . merged_settings . fast_connection != Some ( true ) {
277
+ copy_command. arg ( "--substitute-on-destination" ) ;
278
+ }
279
+
280
+ if !data. check_sigs {
281
+ copy_command. arg ( "--no-check-sigs" ) ;
282
+ }
283
+
284
+ let hostname = match data. deploy_data . cmd_overrides . hostname {
285
+ Some ( ref x) => x,
286
+ None => & data. deploy_data . node . node_settings . hostname ,
287
+ } ;
288
+
289
+ let copy_exit_status = copy_command
290
+ . arg ( "--to" )
291
+ . arg ( format ! ( "ssh://{}@{}" , data. deploy_defs. ssh_user, hostname) )
292
+ . arg ( & data. deploy_data . profile . profile_settings . path )
293
+ . env ( "NIX_SSHOPTS" , ssh_opts_str)
294
+ . status ( )
295
+ . await
296
+ . map_err ( PushProfileError :: Copy ) ?;
297
+
298
+ match copy_exit_status. code ( ) {
299
+ Some ( 0 ) => ( ) ,
300
+ a => return Err ( PushProfileError :: CopyExit ( a) ) ,
301
+ } ;
302
+ }
303
+
228
304
Ok ( ( ) )
229
305
}
0 commit comments