@@ -82,22 +82,8 @@ const PREPPN: u32 = 1;
82
82
#[ cfg( target_arch = "ppc64" ) ]
83
83
const RESERVEDPN : u32 = 1 ;
84
84
85
- /// Perform an upgrade operation
86
- #[ derive( Debug , Clone , clap:: Parser ) ]
87
- pub ( crate ) struct InstallOpts {
88
- /// Target block device for installation. The entire device will be wiped.
89
- pub ( crate ) device : Utf8PathBuf ,
90
-
91
- /// Automatically wipe all existing data on device
92
- #[ clap( long) ]
93
- pub ( crate ) wipe : bool ,
94
-
95
- /// Size of the root partition (default specifier: M). Allowed specifiers: M (mebibytes), G (gibibytes), T (tebibytes).
96
- ///
97
- /// By default, all remaining space on the disk will be used.
98
- #[ clap( long) ]
99
- pub ( crate ) root_size : Option < String > ,
100
-
85
+ #[ derive( clap:: Args , Debug , Clone ) ]
86
+ pub ( crate ) struct InstallTargetOpts {
101
87
// TODO: A size specifier which allocates free space for the root in *addition* to the base container image size
102
88
// pub(crate) root_additional_size: Option<String>
103
89
/// The transport; e.g. oci, oci-archive. Defaults to `registry`.
@@ -115,11 +101,10 @@ pub(crate) struct InstallOpts {
115
101
/// Enable verification via an ostree remote
116
102
#[ clap( long) ]
117
103
pub ( crate ) target_ostree_remote : Option < String > ,
104
+ }
118
105
119
- /// Target root filesystem type.
120
- #[ clap( long, value_enum, default_value_t) ]
121
- pub ( crate ) filesystem : Filesystem ,
122
-
106
+ #[ derive( clap:: Args , Debug , Clone ) ]
107
+ pub ( crate ) struct InstallConfigOpts {
123
108
/// Path to an Ignition config file
124
109
#[ clap( long, value_parser) ]
125
110
pub ( crate ) ignition_file : Option < Utf8PathBuf > ,
@@ -131,13 +116,6 @@ pub(crate) struct InstallOpts {
131
116
#[ clap( long, value_name = "digest" , value_parser) ]
132
117
pub ( crate ) ignition_hash : Option < crate :: ignition:: IgnitionHash > ,
133
118
134
- /// Target root block device setup.
135
- ///
136
- /// direct: Filesystem written directly to block device
137
- /// tpm2-luks: Bind unlock of filesystem to presence of the default tpm2 device.
138
- #[ clap( long, value_enum, default_value_t) ]
139
- pub ( crate ) block_setup : BlockSetup ,
140
-
141
119
/// Disable SELinux in the target (installed) system.
142
120
///
143
121
/// This is currently necessary to install *from* a system with SELinux disabled
@@ -154,9 +132,54 @@ pub(crate) struct InstallOpts {
154
132
karg : Option < Vec < String > > ,
155
133
}
156
134
135
+ /// Options for installing to a block device
136
+ #[ derive( Debug , Clone , clap:: Args ) ]
137
+ pub ( crate ) struct InstallBlockDeviceOpts {
138
+ /// Target block device for installation. The entire device will be wiped.
139
+ pub ( crate ) device : Utf8PathBuf ,
140
+
141
+ /// Automatically wipe all existing data on device
142
+ #[ clap( long) ]
143
+ pub ( crate ) wipe : bool ,
144
+
145
+ /// Target root block device setup.
146
+ ///
147
+ /// direct: Filesystem written directly to block device
148
+ /// tpm2-luks: Bind unlock of filesystem to presence of the default tpm2 device.
149
+ #[ clap( long, value_enum, default_value_t) ]
150
+ pub ( crate ) block_setup : BlockSetup ,
151
+
152
+ /// Target root filesystem type.
153
+ #[ clap( long, value_enum, default_value_t) ]
154
+ pub ( crate ) filesystem : Filesystem ,
155
+
156
+ /// Size of the root partition (default specifier: M). Allowed specifiers: M (mebibytes), G (gibibytes), T (tebibytes).
157
+ ///
158
+ /// By default, all remaining space on the disk will be used.
159
+ #[ clap( long) ]
160
+ pub ( crate ) root_size : Option < String > ,
161
+ }
162
+
163
+ /// Perform an installation to a block device.
164
+ #[ derive( Debug , Clone , clap:: Parser ) ]
165
+ pub ( crate ) struct InstallOpts {
166
+ #[ clap( flatten) ]
167
+ pub ( crate ) block_opts : InstallBlockDeviceOpts ,
168
+
169
+ #[ clap( flatten) ]
170
+ pub ( crate ) target_opts : InstallTargetOpts ,
171
+
172
+ #[ clap( flatten) ]
173
+ pub ( crate ) config_opts : InstallConfigOpts ,
174
+ }
175
+
157
176
// Shared read-only global state
158
177
struct State {
159
- opts : InstallOpts ,
178
+ container_info : ContainerExecutionInfo ,
179
+ /// Force SELinux off in target system
180
+ override_disable_selinux : bool ,
181
+ config_opts : InstallConfigOpts ,
182
+ target_opts : InstallTargetOpts ,
160
183
/// Path to our devtmpfs
161
184
devdir : Utf8PathBuf ,
162
185
mntdir : Utf8PathBuf ,
@@ -251,23 +274,21 @@ fn bind_mount_from_host(src: impl AsRef<Utf8Path>, dest: impl AsRef<Utf8Path>) -
251
274
#[ context( "Creating ostree deployment" ) ]
252
275
async fn initialize_ostree_root_from_self (
253
276
state : & State ,
254
- containerstate : & ContainerExecutionInfo ,
255
277
root_setup : & RootSetup ,
256
- kargs : & [ & str ] ,
257
278
) -> Result < InstallAleph > {
258
279
let rootfs_dir = & root_setup. rootfs_fd ;
259
280
let rootfs = root_setup. rootfs . as_path ( ) ;
260
- let opts = & state. opts ;
281
+ let opts = & state. target_opts ;
261
282
let cancellable = gio:: Cancellable :: NONE ;
262
283
263
- if !containerstate . engine . starts_with ( "podman" ) {
284
+ if !state . container_info . engine . starts_with ( "podman" ) {
264
285
anyhow:: bail!( "Currently this command only supports being executed via podman" ) ;
265
286
}
266
- if containerstate . imageid . is_empty ( ) {
287
+ if state . container_info . imageid . is_empty ( ) {
267
288
anyhow:: bail!( "Invalid empty imageid" ) ;
268
289
}
269
- let digest = crate :: podman:: imageid_to_digest ( & containerstate . imageid ) ?;
270
- let src_image = crate :: utils:: digested_pullspec ( & containerstate . image , & digest) ;
290
+ let digest = crate :: podman:: imageid_to_digest ( & state . container_info . imageid ) ?;
291
+ let src_image = crate :: utils:: digested_pullspec ( & state . container_info . image , & digest) ;
271
292
272
293
let src_imageref = ostree_container:: OstreeImageReference {
273
294
sigverify : ostree_container:: SignatureSource :: ContainerPolicyAllowInsecure ,
@@ -300,7 +321,7 @@ async fn initialize_ostree_root_from_self(
300
321
sigverify : target_sigverify,
301
322
imgref : ostree_container:: ImageReference {
302
323
transport : ostree_container:: Transport :: Registry ,
303
- name : containerstate . image . clone ( ) ,
324
+ name : state . container_info . image . clone ( ) ,
304
325
} ,
305
326
}
306
327
} ;
@@ -349,9 +370,14 @@ async fn initialize_ostree_root_from_self(
349
370
r
350
371
} ;
351
372
373
+ let kargs = root_setup
374
+ . kargs
375
+ . iter ( )
376
+ . map ( |v| v. as_str ( ) )
377
+ . collect :: < Vec < _ > > ( ) ;
352
378
#[ allow( clippy:: needless_update) ]
353
379
let options = ostree_container:: deploy:: DeployOpts {
354
- kargs : Some ( kargs) ,
380
+ kargs : Some ( kargs. as_slice ( ) ) ,
355
381
target_imgref : Some ( & target_imgref) ,
356
382
proxy_cfg : Some ( proxy_cfg) ,
357
383
..Default :: default ( )
@@ -461,8 +487,7 @@ struct RootSetup {
461
487
}
462
488
463
489
#[ context( "Creating rootfs" ) ]
464
- fn install_create_rootfs ( state : & State ) -> Result < RootSetup > {
465
- let opts = & state. opts ;
490
+ fn install_create_rootfs ( state : & State , opts : InstallBlockDeviceOpts ) -> Result < RootSetup > {
466
491
// Verify that the target is empty (if not already wiped in particular, but it's
467
492
// also good to verify that the wipe worked)
468
493
let device = crate :: blockdev:: list_dev ( & opts. device ) ?;
@@ -680,11 +705,11 @@ pub(crate) fn finalize_filesystem(fs: &Utf8Path) -> Result<()> {
680
705
Ok ( ( ) )
681
706
}
682
707
683
- /// Implementation of the `bootc install` CLI command .
684
- pub ( crate ) async fn install ( opts : InstallOpts ) -> Result < ( ) > {
685
- // This command currently *must* be run inside a privileged container.
686
- let container_state = crate :: containerenv :: get_container_execution_info ( ) ? ;
687
-
708
+ /// Preparation for an install; validates and prepares some (thereafter immutable) global state .
709
+ async fn prepare_install (
710
+ config_opts : InstallConfigOpts ,
711
+ target_opts : InstallTargetOpts ,
712
+ ) -> Result < Arc < State > > {
688
713
// We require --pid=host
689
714
let pid = std:: fs:: read_link ( "/proc/1/exe" ) . context ( "reading /proc/1/exe" ) ?;
690
715
let pid = pid
@@ -694,6 +719,9 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
694
719
anyhow:: bail!( "This command must be run with --pid=host" )
695
720
}
696
721
722
+ // This command currently *must* be run inside a privileged container.
723
+ let container_info = crate :: containerenv:: get_container_execution_info ( ) ?;
724
+
697
725
// Even though we require running in a container, the mounts we create should be specific
698
726
// to this process, so let's enter a private mountns to avoid leaking them.
699
727
if std:: env:: var_os ( "BOOTC_SKIP_UNSHARE" ) . is_none ( ) {
@@ -725,7 +753,7 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
725
753
crate :: lsm:: container_setup_selinux ( ) ?;
726
754
// This will re-execute the current process (once).
727
755
crate :: lsm:: selinux_ensure_install ( ) ?;
728
- } else if opts . disable_selinux {
756
+ } else if config_opts . disable_selinux {
729
757
override_disable_selinux = true ;
730
758
println ! ( "notice: Target has SELinux enabled, overriding to disable" )
731
759
} else {
@@ -755,31 +783,43 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
755
783
// Overmount /var/tmp with the host's, so we can use it to share state
756
784
bind_mount_from_host ( "/var/tmp" , "/var/tmp" ) ?;
757
785
let state = Arc :: new ( State {
786
+ override_disable_selinux,
787
+ container_info,
758
788
mntdir,
759
789
devdir,
760
- opts,
790
+ config_opts,
791
+ target_opts,
761
792
} ) ;
762
793
794
+ Ok ( state)
795
+ }
796
+
797
+ /// Implementation of the `bootc install` CLI command.
798
+ pub ( crate ) async fn install ( opts : InstallOpts ) -> Result < ( ) > {
799
+ let block_opts = opts. block_opts ;
800
+ let state = prepare_install ( opts. config_opts , opts. target_opts ) . await ?;
801
+
763
802
// This is all blocking stuff
764
- let rootfs = {
803
+ let mut rootfs = {
765
804
let state = state. clone ( ) ;
766
- tokio:: task:: spawn_blocking ( move || install_create_rootfs ( & state) ) . await ??
805
+ tokio:: task:: spawn_blocking ( move || install_create_rootfs ( & state, block_opts ) ) . await ??
767
806
} ;
768
- let mut kargs = rootfs. kargs . iter ( ) . map ( |v| v. as_str ( ) ) . collect :: < Vec < _ > > ( ) ;
769
- if override_disable_selinux {
770
- kargs. push ( "selinux=0" ) ;
807
+ if state. override_disable_selinux {
808
+ rootfs. kargs . push ( "selinux=0" . to_string ( ) ) ;
771
809
}
772
810
// This is interpreted by our GRUB fragment
773
- if state. opts . ignition_file . is_some ( ) {
774
- kargs. push ( crate :: ignition:: PLATFORM_METAL_KARG ) ;
775
- kargs. push ( crate :: bootloader:: IGNITION_VARIABLE ) ;
811
+ if state. config_opts . ignition_file . is_some ( ) {
812
+ rootfs
813
+ . kargs
814
+ . push ( crate :: ignition:: PLATFORM_METAL_KARG . to_string ( ) ) ;
815
+ rootfs
816
+ . kargs
817
+ . push ( crate :: bootloader:: IGNITION_VARIABLE . to_string ( ) ) ;
776
818
}
777
819
778
820
// Write the aleph data that captures the system state at the time of provisioning for aid in future debugging.
779
821
{
780
- let aleph =
781
- initialize_ostree_root_from_self ( & state, & container_state, & rootfs, kargs. as_slice ( ) )
782
- . await ?;
822
+ let aleph = initialize_ostree_root_from_self ( & state, & rootfs) . await ?;
783
823
rootfs
784
824
. rootfs_fd
785
825
. atomic_replace_with ( BOOTC_ALEPH_PATH , |f| {
@@ -792,11 +832,11 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {
792
832
crate :: bootloader:: install_via_bootupd ( & rootfs. device , & rootfs. rootfs , & rootfs. boot_uuid ) ?;
793
833
794
834
// If Ignition is specified, enable it
795
- if let Some ( ignition_file) = state. opts . ignition_file . as_deref ( ) {
835
+ if let Some ( ignition_file) = state. config_opts . ignition_file . as_deref ( ) {
796
836
let src = std:: fs:: File :: open ( ignition_file)
797
837
. with_context ( || format ! ( "Opening {ignition_file}" ) ) ?;
798
838
let bootfs = rootfs. rootfs . join ( "boot" ) ;
799
- crate :: ignition:: write_ignition ( & bootfs, & state. opts . ignition_hash , & src) ?;
839
+ crate :: ignition:: write_ignition ( & bootfs, & state. config_opts . ignition_hash , & src) ?;
800
840
crate :: ignition:: enable_firstboot ( & bootfs) ?;
801
841
println ! ( "Installed Ignition config from {ignition_file}" ) ;
802
842
}
0 commit comments