Skip to main content

InstallPlan

Struct InstallPlan 

Source
pub struct InstallPlan { /* private fields */ }
Expand description

An explicit installation root and a coherent artifact set ready for private preparation.

Implementations§

Source§

impl InstallPlan

Source

pub fn new( product: ProductId, release: ReleaseId, installation_root: impl Into<PathBuf>, artifacts: ArtifactSet, ) -> Result<Self>

Validates an installation root, destination containment, sources, and member uniqueness.

Examples found in repository?
examples/receipts.rs (lines 51-56)
35fn build(
36    new: &[u8],
37    old: Option<&[u8]>,
38) -> Result<(PathBuf, InstallPlan), Box<dyn std::error::Error>> {
39    let base = temp_root("eggup-receipts");
40    let inputs = base.join("inputs");
41    let root = base.join("install");
42    fs::create_dir_all(&inputs)?;
43    fs::create_dir_all(root.join("bin"))?;
44    fs::write(inputs.join("app"), new)?;
45    if let Some(old) = old {
46        fs::write(root.join("bin/app"), old)?;
47    }
48    let digest = eggup_core::hash_file(&inputs.join("app"))?;
49    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
50        .with_integrity(IntegrityRequirement::Sha256(digest));
51    let plan = InstallPlan::new(
52        ProductId::new("demo")?,
53        ReleaseId::new("5.0.0")?,
54        &root,
55        ArtifactSet::single(member)?,
56    )?;
57    // Leak the base via the plan's root; caller cleans `root.parent()`.
58    Ok((base, plan))
59}
More examples
Hide additional examples
examples/custom_validator.rs (lines 56-61)
46fn main() -> Result<(), Box<dyn std::error::Error>> {
47    let base = temp_root("eggup-custom-validator");
48    let inputs = base.join("inputs");
49    let root = base.join("install");
50    fs::create_dir_all(&inputs)?;
51    fs::create_dir_all(root.join("bin"))?;
52    fs::write(inputs.join("app"), b"release-marker-42")?;
53    let digest = eggup_core::hash_file(&inputs.join("app"))?;
54    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
55        .with_integrity(IntegrityRequirement::Sha256(digest));
56    let plan = InstallPlan::new(
57        ProductId::new("demo")?,
58        ReleaseId::new("3.0.0")?,
59        &root,
60        ArtifactSet::single(member)?,
61    )?;
62    let validator = ContainsMarker {
63        member: MemberId::new("main")?,
64        marker: b"marker-42".to_vec(),
65    };
66    let receipt = plan
67        .prepare()?
68        .verify_integrity()?
69        .validate(&validator)?
70        .commit(CommitOwnership::new(
71            &ExistingAsOwnedVerifier,
72            AbsentPolicy::AllowCreate,
73        ))?;
74    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
75    let _ = fs::remove_dir_all(&base);
76    println!("custom validator commit: {:?}", receipt.disposition());
77    Ok(())
78}
examples/one_member.rs (lines 44-49)
30fn main() -> Result<(), Box<dyn std::error::Error>> {
31    let base = temp_root("eggup-quickstart");
32    let (inputs, root) = (base.join("inputs"), base.join("install"));
33    fs::create_dir_all(inputs.join("bin"))?;
34    fs::create_dir_all(root.join("bin"))?;
35    fs::write(inputs.join("app"), b"new-app")?;
36
37    // 1. Describe one member, with the SHA-256 you expect to verify.
38    let source = inputs.join("app");
39    let digest = eggup_core::hash_file(&source)?;
40    let member = ArtifactMember::new(MemberId::new("main")?, source, "bin/app")?
41        .with_integrity(IntegrityRequirement::Sha256(digest));
42
43    // 2. Name the product, release, install root, and artifact set.
44    let plan = InstallPlan::new(
45        ProductId::new("demo")?,
46        ReleaseId::new("1.0.0")?,
47        &root,
48        ArtifactSet::single(member)?,
49    )?;
50
51    // 3. Stage, verify, validate, then commit with proven ownership.
52    let receipt = plan
53        .prepare()?
54        .verify_integrity()?
55        .validate(&AllValidators::new())?
56        .commit(CommitOwnership::new(
57            &ExistingAsOwnedVerifier,
58            AbsentPolicy::AllowCreate,
59        ))?;
60
61    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
62    assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
63    let _ = fs::remove_dir_all(&base);
64    println!("committed: {:?}", receipt.disposition());
65    Ok(())
66}
examples/ownership.rs (lines 36-41)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25    let base = temp_root("eggup-ownership");
26    let inputs = base.join("inputs");
27    let root = base.join("install");
28    fs::create_dir_all(&inputs)?;
29    fs::create_dir_all(root.join("bin"))?;
30    fs::write(inputs.join("app"), b"new-app")?;
31    fs::write(root.join("bin/app"), b"old-app")?;
32    let new_digest = eggup_core::hash_file(&inputs.join("app"))?;
33    let old_digest = eggup_core::hash_file(&root.join("bin/app"))?;
34    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
35        .with_integrity(IntegrityRequirement::Sha256(new_digest));
36    let plan = InstallPlan::new(
37        ProductId::new("demo")?,
38        ReleaseId::new("4.0.0")?,
39        &root,
40        ArtifactSet::single(member)?,
41    )?;
42    // Prove the live file is the expected old deployment; deny creation of
43    // absent destinations (replacement-only policy).
44    let verifier = ExactDigestVerifier::new(vec![(MemberId::new("main")?, old_digest)]);
45    let receipt = plan
46        .prepare()?
47        .verify_integrity()?
48        .validate(&AllValidators::new())?
49        .commit(CommitOwnership::new(&verifier, AbsentPolicy::DenyCreate))?;
50    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
51    assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
52    let _ = fs::remove_dir_all(&base);
53    println!("ownership-verified commit: {:?}", receipt.disposition());
54    Ok(())
55}
examples/multi_member.rs (lines 46-51)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25    let base = temp_root("eggup-multi-member");
26    let inputs = base.join("inputs");
27    let root = base.join("install");
28    fs::create_dir_all(&inputs)?;
29    fs::create_dir_all(root.join("bin"))?;
30    fs::write(inputs.join("main"), b"new-main")?;
31    fs::write(inputs.join("helper"), b"new-helper")?;
32    fs::write(root.join("bin/main"), b"old-main")?;
33    fs::write(root.join("bin/helper"), b"old-helper")?;
34    let main_digest = eggup_core::hash_file(&inputs.join("main"))?;
35    let helper_digest = eggup_core::hash_file(&inputs.join("helper"))?;
36    let members = ArtifactSet::new(vec![
37        ArtifactMember::new(MemberId::new("main")?, inputs.join("main"), "bin/main")?
38            .with_integrity(IntegrityRequirement::Sha256(main_digest)),
39        ArtifactMember::new(
40            MemberId::new("helper")?,
41            inputs.join("helper"),
42            "bin/helper",
43        )?
44        .with_integrity(IntegrityRequirement::Sha256(helper_digest)),
45    ])?;
46    let plan = InstallPlan::new(
47        ProductId::new("bundle")?,
48        ReleaseId::new("2.0.0")?,
49        &root,
50        members,
51    )?;
52    let receipt = plan
53        .prepare()?
54        .verify_integrity()?
55        .validate(&AllValidators::new())?
56        .commit(CommitOwnership::new(
57            &ExistingAsOwnedVerifier,
58            AbsentPolicy::AllowCreate,
59        ))?;
60    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
61    assert_eq!(fs::read(root.join("bin/main"))?, b"new-main");
62    assert_eq!(fs::read(root.join("bin/helper"))?, b"new-helper");
63    let _ = fs::remove_dir_all(&base);
64    println!("multi-member commit: {:?}", receipt.disposition());
65    Ok(())
66}
Source

pub fn for_current_executable( product: ProductId, release: ReleaseId, candidate: impl Into<PathBuf>, current: &CurrentExecutable, integrity: IntegrityRequirement, ) -> Result<Self>

Builds a one-member plan that replaces the executable currently running.

This is an ordinary InstallPlan, not a second transaction model: it advances through exactly the same prepared/verified/validated/receipt path as any other one-member plan, and its terminal contract is the same crate::TransactionReceipt. The only differences are authority and identity:

  • The installation root is the executable’s own directory, and staging is StagePlacement::InsideInstallationRoot, so the transaction needs write authority in that directory and nowhere above it.
  • The single destination is the canonical live image, so an invocation symlink is followed to its real target instead of being overwritten.
  • The bound executable identity is re-proved immediately before mutation.

The candidate must be a local path outside the executable’s directory, exactly as for any other member. Nothing here selects a release, infers a version, or reads product identity from the executable: product, release, and integrity are all caller-supplied.

Source

pub fn stage_placement(&self) -> StagePlacement

Returns where this plan creates its private staging state.

Source

pub fn current_executable(&self) -> Option<&CurrentExecutable>

Returns the bound current executable when this plan replaces one.

Source

pub fn product(&self) -> &ProductId

Returns the consumer-owned product identity.

Source

pub fn release(&self) -> &ReleaseId

Returns the consumer-owned release identity.

Source

pub fn installation_root(&self) -> &Path

Returns the exact installation root used for destination resolution.

Source

pub fn artifacts(&self) -> &ArtifactSet

Returns the validated artifact set.

Source

pub fn destination(&self, member: &MemberId) -> Result<PathBuf>

Resolves a member destination beneath the exact installation root.

Source

pub fn prepare(self) -> Result<PreparedTransaction>

Copies every member into private owned stage state without mutating a destination.

Examples found in repository?
examples/custom_validator.rs (line 67)
46fn main() -> Result<(), Box<dyn std::error::Error>> {
47    let base = temp_root("eggup-custom-validator");
48    let inputs = base.join("inputs");
49    let root = base.join("install");
50    fs::create_dir_all(&inputs)?;
51    fs::create_dir_all(root.join("bin"))?;
52    fs::write(inputs.join("app"), b"release-marker-42")?;
53    let digest = eggup_core::hash_file(&inputs.join("app"))?;
54    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
55        .with_integrity(IntegrityRequirement::Sha256(digest));
56    let plan = InstallPlan::new(
57        ProductId::new("demo")?,
58        ReleaseId::new("3.0.0")?,
59        &root,
60        ArtifactSet::single(member)?,
61    )?;
62    let validator = ContainsMarker {
63        member: MemberId::new("main")?,
64        marker: b"marker-42".to_vec(),
65    };
66    let receipt = plan
67        .prepare()?
68        .verify_integrity()?
69        .validate(&validator)?
70        .commit(CommitOwnership::new(
71            &ExistingAsOwnedVerifier,
72            AbsentPolicy::AllowCreate,
73        ))?;
74    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
75    let _ = fs::remove_dir_all(&base);
76    println!("custom validator commit: {:?}", receipt.disposition());
77    Ok(())
78}
More examples
Hide additional examples
examples/one_member.rs (line 53)
30fn main() -> Result<(), Box<dyn std::error::Error>> {
31    let base = temp_root("eggup-quickstart");
32    let (inputs, root) = (base.join("inputs"), base.join("install"));
33    fs::create_dir_all(inputs.join("bin"))?;
34    fs::create_dir_all(root.join("bin"))?;
35    fs::write(inputs.join("app"), b"new-app")?;
36
37    // 1. Describe one member, with the SHA-256 you expect to verify.
38    let source = inputs.join("app");
39    let digest = eggup_core::hash_file(&source)?;
40    let member = ArtifactMember::new(MemberId::new("main")?, source, "bin/app")?
41        .with_integrity(IntegrityRequirement::Sha256(digest));
42
43    // 2. Name the product, release, install root, and artifact set.
44    let plan = InstallPlan::new(
45        ProductId::new("demo")?,
46        ReleaseId::new("1.0.0")?,
47        &root,
48        ArtifactSet::single(member)?,
49    )?;
50
51    // 3. Stage, verify, validate, then commit with proven ownership.
52    let receipt = plan
53        .prepare()?
54        .verify_integrity()?
55        .validate(&AllValidators::new())?
56        .commit(CommitOwnership::new(
57            &ExistingAsOwnedVerifier,
58            AbsentPolicy::AllowCreate,
59        ))?;
60
61    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
62    assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
63    let _ = fs::remove_dir_all(&base);
64    println!("committed: {:?}", receipt.disposition());
65    Ok(())
66}
examples/ownership.rs (line 46)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25    let base = temp_root("eggup-ownership");
26    let inputs = base.join("inputs");
27    let root = base.join("install");
28    fs::create_dir_all(&inputs)?;
29    fs::create_dir_all(root.join("bin"))?;
30    fs::write(inputs.join("app"), b"new-app")?;
31    fs::write(root.join("bin/app"), b"old-app")?;
32    let new_digest = eggup_core::hash_file(&inputs.join("app"))?;
33    let old_digest = eggup_core::hash_file(&root.join("bin/app"))?;
34    let member = ArtifactMember::new(MemberId::new("main")?, inputs.join("app"), "bin/app")?
35        .with_integrity(IntegrityRequirement::Sha256(new_digest));
36    let plan = InstallPlan::new(
37        ProductId::new("demo")?,
38        ReleaseId::new("4.0.0")?,
39        &root,
40        ArtifactSet::single(member)?,
41    )?;
42    // Prove the live file is the expected old deployment; deny creation of
43    // absent destinations (replacement-only policy).
44    let verifier = ExactDigestVerifier::new(vec![(MemberId::new("main")?, old_digest)]);
45    let receipt = plan
46        .prepare()?
47        .verify_integrity()?
48        .validate(&AllValidators::new())?
49        .commit(CommitOwnership::new(&verifier, AbsentPolicy::DenyCreate))?;
50    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
51    assert_eq!(fs::read(root.join("bin/app"))?, b"new-app");
52    let _ = fs::remove_dir_all(&base);
53    println!("ownership-verified commit: {:?}", receipt.disposition());
54    Ok(())
55}
examples/multi_member.rs (line 53)
24fn main() -> Result<(), Box<dyn std::error::Error>> {
25    let base = temp_root("eggup-multi-member");
26    let inputs = base.join("inputs");
27    let root = base.join("install");
28    fs::create_dir_all(&inputs)?;
29    fs::create_dir_all(root.join("bin"))?;
30    fs::write(inputs.join("main"), b"new-main")?;
31    fs::write(inputs.join("helper"), b"new-helper")?;
32    fs::write(root.join("bin/main"), b"old-main")?;
33    fs::write(root.join("bin/helper"), b"old-helper")?;
34    let main_digest = eggup_core::hash_file(&inputs.join("main"))?;
35    let helper_digest = eggup_core::hash_file(&inputs.join("helper"))?;
36    let members = ArtifactSet::new(vec![
37        ArtifactMember::new(MemberId::new("main")?, inputs.join("main"), "bin/main")?
38            .with_integrity(IntegrityRequirement::Sha256(main_digest)),
39        ArtifactMember::new(
40            MemberId::new("helper")?,
41            inputs.join("helper"),
42            "bin/helper",
43        )?
44        .with_integrity(IntegrityRequirement::Sha256(helper_digest)),
45    ])?;
46    let plan = InstallPlan::new(
47        ProductId::new("bundle")?,
48        ReleaseId::new("2.0.0")?,
49        &root,
50        members,
51    )?;
52    let receipt = plan
53        .prepare()?
54        .verify_integrity()?
55        .validate(&AllValidators::new())?
56        .commit(CommitOwnership::new(
57            &ExistingAsOwnedVerifier,
58            AbsentPolicy::AllowCreate,
59        ))?;
60    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
61    assert_eq!(fs::read(root.join("bin/main"))?, b"new-main");
62    assert_eq!(fs::read(root.join("bin/helper"))?, b"new-helper");
63    let _ = fs::remove_dir_all(&base);
64    println!("multi-member commit: {:?}", receipt.disposition());
65    Ok(())
66}
examples/receipts.rs (line 65)
61fn main() -> Result<(), Box<dyn std::error::Error>> {
62    // 1. Committed: success.
63    let (base, plan) = build(b"new", Some(b"old"))?;
64    let receipt = plan
65        .prepare()?
66        .verify_integrity()?
67        .validate(&AllValidators::new())?
68        .commit(CommitOwnership::new(
69            &ExistingAsOwnedVerifier,
70            AbsentPolicy::AllowCreate,
71        ))?;
72    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
73    assert!(receipt.failure().is_none());
74    println!("committed: {:?}", receipt.disposition());
75    let _ = fs::remove_dir_all(&base);
76
77    // 2. RolledBack: ownership denied, live state unchanged, failure explains why.
78    let (base, plan) = build(b"new", Some(b"old"))?;
79    let root = base.join("install");
80    let receipt = plan
81        .prepare()?
82        .verify_integrity()?
83        .validate(&AllValidators::new())?
84        .commit(CommitOwnership::new(&DenyAll, AbsentPolicy::AllowCreate))?;
85    assert_eq!(receipt.disposition(), TransactionDisposition::RolledBack);
86    assert!(receipt.rollback_verified());
87    let failure = receipt.failure().expect("rolled back carries a cause");
88    println!(
89        "rolled back: phase={} category={} detail={}",
90        failure.phase().as_str(),
91        failure.category().as_str(),
92        failure.detail()
93    );
94    assert_eq!(fs::read(root.join("bin/app"))?, b"old");
95    assert_ne!(
96        receipt.disposition(),
97        TransactionDisposition::Committed,
98        "rollback is not success"
99    );
100    let _ = fs::remove_dir_all(&base);
101
102    // 3. RecoveryRequired is distinct and high severity: it means Eggup cannot
103    // prove either generation is coherent. It is produced by rollback failure
104    // or unclean finalization; operators must inspect `recovery_path` and the
105    // preserved lock. See `docs/transaction.md` for the full contract.
106    println!("recovery-required: inspect recovery_path + lock; do not retry blindly");
107
108    // 4. A failed post-commit check can keep the coherent new generation,
109    // while remaining machine-readable as a failed check.
110    let (base, plan) = build(b"new", Some(b"old"))?;
111    let receipt = plan
112        .prepare()?
113        .verify_integrity()?
114        .validate(&AllValidators::new())?
115        .commit_with_post_commit(
116            CommitOwnership::new(&ExistingAsOwnedVerifier, AbsentPolicy::AllowCreate),
117            PostCommitFailurePolicy::KeepInstalled,
118            || Err("example health check failed"),
119        )?;
120    assert_eq!(receipt.disposition(), TransactionDisposition::Committed);
121    assert!(receipt.post_commit_failure().is_some());
122    println!(
123        "kept installed after failed check: {:?}",
124        receipt.disposition()
125    );
126    let _ = fs::remove_dir_all(base);
127    Ok(())
128}
Source

pub fn prepare_with_bound_sources( self, bound: BoundSources, ) -> Result<PreparedTransaction>

Copies every member into private owned stage state, sourcing bound members from their already-open objects without any pathname lookup.

Each member identity present in bound is staged by reading the moved open object from byte zero; its recorded source path is treated as advisory diagnostics only and is never opened, even if it now names foreign state. Members absent from bound stage from their recorded source paths exactly as in prepare.

Every bound identity must name a member of this plan: leftover handles fail closed and no staged byte is ever obtained by reopening a member name as fallback. A failed bound stage copy never falls back to the recorded path.

Trait Implementations§

Source§

impl Clone for InstallPlan

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for InstallPlan

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for InstallPlan

Source§

impl PartialEq for InstallPlan

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for InstallPlan

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.