@@ -205,11 +205,25 @@ pub fn build_canopy_restore_job(cfg: &CanopyRestoreJobConfig<'_>) -> Job {
205205 args : Some ( vec ! [ script] ) ,
206206 env : Some (
207207 [
208- vec ! [ EnvVar {
209- name: "SNAPSHOT_ID" . into( ) ,
210- value: Some ( cfg. snapshot_id. into( ) ) ,
211- ..Default :: default ( )
212- } ] ,
208+ vec ! [
209+ EnvVar {
210+ name: "SNAPSHOT_ID" . into( ) ,
211+ value: Some ( cfg. snapshot_id. into( ) ) ,
212+ ..Default :: default ( )
213+ } ,
214+ // `snapshot restore` runs as a separate kopia process from
215+ // `repository connect`, so it must re-open the repository and
216+ // needs the password. Headless containers have no keyring for
217+ // kopia to retrieve the persisted credential from, so without
218+ // this env var it falls back to an interactive prompt and dies
219+ // with "inappropriate ioctl for device". kopia reads
220+ // KOPIA_PASSWORD on every invocation.
221+ EnvVar {
222+ name: "KOPIA_PASSWORD" . into( ) ,
223+ value: Some ( cfg. repo_password. into( ) ) ,
224+ ..Default :: default ( )
225+ } ,
226+ ] ,
213227 kopia_writable_env ( ) ,
214228 ]
215229 . concat ( ) ,
@@ -636,6 +650,14 @@ mod tests {
636650 . iter( )
637651 . any( |e| e. name == "SNAPSHOT_ID" && e. value. as_deref( ) == Some ( "abc123" ) )
638652 ) ;
653+ // KOPIA_PASSWORD must be on the container env so `snapshot restore`
654+ // (a separate process from `repository connect`) can re-open the
655+ // repository without an interactive password prompt.
656+ assert ! (
657+ kopia_env
658+ . iter( )
659+ . any( |e| e. name == "KOPIA_PASSWORD" && e. value. as_deref( ) == Some ( "supersecret" ) )
660+ ) ;
639661 let kopia_mounts = pod_spec. containers [ 0 ] . volume_mounts . as_ref ( ) . unwrap ( ) ;
640662 assert ! (
641663 kopia_mounts
0 commit comments