Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add split-receipt command that splits receipt into phase 1 and 2 uninstallation flows #1278

Merged
merged 19 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fdab69c
fixup: align ActionTag with serde and typetag tags
cole-h Oct 31, 2024
5a672b6
Bump to 0.28.0, since receipt format changed due to misaligned serde …
cole-h Nov 8, 2024
3c2e262
fixup: make it possible to write receipts for anything serializable
cole-h Nov 8, 2024
d48d279
Leave TODO about existing macos volume services being different
cole-h Nov 4, 2024
fd2122c
Remove determinate/upstream init service if it still exists
cole-h Nov 7, 2024
d2ff10a
Check that the GID of an existing /nix/store is the same GID as we're…
cole-h Nov 7, 2024
de840c4
fixup: make some fields pub(crate) for the upcoming split-receipt com…
cole-h Nov 8, 2024
bb478cf
fixup: reorganize imports
cole-h Nov 1, 2024
5300845
Add `split-receipt` command that splits receipt into phase 1 and 2 un…
cole-h Nov 1, 2024
327928e
Remove phase 1 and 2 uninstall receipts if they exist upon a successf…
cole-h Nov 1, 2024
ee19b2b
Move split_receipt module into its own file
cole-h Nov 8, 2024
d4dcc07
fixup: cargo clippy
cole-h Nov 8, 2024
ae85929
fixup: extract "check existing nix store gid" into own function
cole-h Nov 8, 2024
531ddd4
fixup: define roundtrip_to_extract_type outside of other function
cole-h Nov 8, 2024
e4ebb45
fixup: don't fail profile deserialization if UnknownProfileItem is mi…
cole-h Nov 8, 2024
b2dfb8d
fixup: put uninstall phase1 command on new line
cole-h Nov 8, 2024
70e9723
Update fixtures
cole-h Nov 8, 2024
e43ca23
fixup: split_receipt: split match expression to separate let
colemickens Nov 12, 2024
e5ce505
fixup: roundtrip_to_exact_type: use type_name instead of manually pas…
colemickens Nov 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "nix-installer"
description = "The Determinate Nix Installer"
version = "0.27.1"
version = "0.28.0"
edition = "2021"
resolver = "2"
license = "LGPL-2.1"
Expand Down
2 changes: 1 addition & 1 deletion src/action/base/create_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ If `force_prune_on_revert` is set, the folder will always be deleted on
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "create_directory")]
pub struct CreateDirectory {
path: PathBuf,
pub(crate) path: PathBuf,
user: Option<String>,
group: Option<String>,
mode: Option<u32>,
Expand Down
29 changes: 27 additions & 2 deletions src/action/common/configure_determinate_nixd_init_service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use crate::settings::InitSystem;
const LINUX_NIXD_DAEMON_DEST: &str = "/etc/systemd/system/nix-daemon.service";

// Darwin
const DARWIN_NIXD_DAEMON_DEST: &str = "/Library/LaunchDaemons/systems.determinate.nix-daemon.plist";
pub(crate) const DARWIN_NIXD_DAEMON_DEST: &str =
"/Library/LaunchDaemons/systems.determinate.nix-daemon.plist";
const DARWIN_NIXD_SERVICE_NAME: &str = "systems.determinate.nix-daemon";

/**
Expand All @@ -37,7 +38,31 @@ impl ConfigureDeterminateNixdInitService {
start_daemon: bool,
) -> Result<StatefulAction<Self>, ActionError> {
let service_dest: Option<PathBuf> = match init {
InitSystem::Launchd => Some(DARWIN_NIXD_DAEMON_DEST.into()),
InitSystem::Launchd => {
// NOTE(cole-h): if the upstream daemon exists and we're installing determinate-
// nixd, we need to remove the old daemon unit -- we used to have a bug[1] where
// these service files wouldn't get removed, so we can't rely on them not being
// there after phase 1 of the uninstall
// [1]: https://github.com/DeterminateSystems/nix-installer/pull/1266
if std::path::Path::new(
super::configure_upstream_init_service::DARWIN_NIX_DAEMON_DEST,
)
.exists()
{
tokio::fs::remove_file(
super::configure_upstream_init_service::DARWIN_NIX_DAEMON_DEST,
)
.await
.map_err(|e| {
Self::error(ActionErrorKind::Remove(
super::configure_upstream_init_service::DARWIN_NIX_DAEMON_DEST.into(),
e,
))
})?;
}

Some(DARWIN_NIXD_DAEMON_DEST.into())
},
InitSystem::Systemd => Some(LINUX_NIXD_DAEMON_DEST.into()),
InitSystem::None => None,
};
Expand Down
31 changes: 28 additions & 3 deletions src/action/common/configure_upstream_init_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::path::PathBuf;

use tracing::{span, Span};

use crate::action::{ActionError, ActionTag, StatefulAction};
use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction};

use crate::action::common::configure_init_service::{SocketFile, UnitSrc};
use crate::action::{common::ConfigureInitService, Action, ActionDescription};
Expand All @@ -15,7 +15,7 @@ const SERVICE_DEST: &str = "/etc/systemd/system/nix-daemon.service";
// Darwin
const DARWIN_NIX_DAEMON_SOURCE: &str =
"/nix/var/nix/profiles/default/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
pub(crate) const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
const DARWIN_LAUNCHD_SERVICE_NAME: &str = "org.nixos.nix-daemon";

/**
Expand All @@ -39,7 +39,32 @@ impl ConfigureUpstreamInitService {
InitSystem::None => None,
};
let service_dest: Option<PathBuf> = match init {
InitSystem::Launchd => Some(DARWIN_NIX_DAEMON_DEST.into()),
InitSystem::Launchd => {
// NOTE(cole-h): if the determinate daemon exists and we're installing the upstream
// daemon, we need to remove the old daemon unit -- we used to have a bug[1] where
// these service files wouldn't get removed, so we can't rely on them not being
// there after phase 1 of the uninstall
// [1]: https://github.com/DeterminateSystems/nix-installer/pull/1266
if std::path::Path::new(
super::configure_determinate_nixd_init_service::DARWIN_NIXD_DAEMON_DEST,
)
.exists()
{
tokio::fs::remove_file(
super::configure_determinate_nixd_init_service::DARWIN_NIXD_DAEMON_DEST,
)
.await
.map_err(|e| {
Self::error(ActionErrorKind::Remove(
super::configure_determinate_nixd_init_service::DARWIN_NIXD_DAEMON_DEST
.into(),
e,
))
})?;
}

Some(DARWIN_NIX_DAEMON_DEST.into())
},
InitSystem::Systemd => Some(SERVICE_DEST.into()),
InitSystem::None => None,
};
Expand Down
36 changes: 33 additions & 3 deletions src/action/common/provision_nix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,31 @@ use crate::{
},
settings::{CommonSettings, SCRATCH_DIR},
};
use std::os::unix::fs::MetadataExt as _;
use std::path::PathBuf;

pub(crate) const NIX_STORE_LOCATION: &str = "/nix/store";

/**
Place Nix and it's requirements onto the target
*/
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "provision_nix")]
pub struct ProvisionNix {
fetch_nix: StatefulAction<FetchAndUnpackNix>,
create_nix_tree: StatefulAction<CreateNixTree>,
move_unpacked_nix: StatefulAction<MoveUnpackedNix>,
pub(crate) fetch_nix: StatefulAction<FetchAndUnpackNix>,
pub(crate) create_nix_tree: StatefulAction<CreateNixTree>,
pub(crate) move_unpacked_nix: StatefulAction<MoveUnpackedNix>,
}

impl ProvisionNix {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
if std::path::Path::new(NIX_STORE_LOCATION).exists() {
cole-h marked this conversation as resolved.
Show resolved Hide resolved
check_existing_nix_store_gid_matches(settings.nix_build_group_id)
.await
.map_err(Self::error)?;
}

let fetch_nix = FetchAndUnpackNix::plan(
settings.nix_package_url.clone(),
PathBuf::from(SCRATCH_DIR),
Expand Down Expand Up @@ -144,3 +153,24 @@ impl Action for ProvisionNix {
}
}
}

/// If there is an existing /nix/store directory, ensure that the group ID we're going to use for
/// the nix build group matches the group that owns /nix/store to prevent weird mismatched-ownership
/// issues.
async fn check_existing_nix_store_gid_matches(
desired_nix_build_group_id: u32,
) -> Result<(), ActionErrorKind> {
let previous_store_metadata = tokio::fs::metadata(NIX_STORE_LOCATION)
.await
.map_err(|e| ActionErrorKind::GettingMetadata(NIX_STORE_LOCATION.into(), e))?;
let previous_store_group_id = previous_store_metadata.gid();
if previous_store_group_id != desired_nix_build_group_id {
return Err(ActionErrorKind::PathGroupMismatch(
NIX_STORE_LOCATION.into(),
previous_store_group_id,
desired_nix_build_group_id,
));
}

Ok(())
}
4 changes: 2 additions & 2 deletions src/action/macos/create_apfs_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::action::{Action, ActionDescription};
use crate::os::darwin::{DiskUtilApfsListOutput, DiskUtilInfoOutput};

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "create_volume")]
#[serde(tag = "action_name", rename = "create_apfs_volume")]
pub struct CreateApfsVolume {
disk: PathBuf,
name: String,
Expand Down Expand Up @@ -53,7 +53,7 @@ impl CreateApfsVolume {
}

#[async_trait::async_trait]
#[typetag::serde(name = "create_volume")]
#[typetag::serde(name = "create_apfs_volume")]
impl Action for CreateApfsVolume {
fn action_tag() -> ActionTag {
ActionTag("create_apfs_volume")
Expand Down
6 changes: 3 additions & 3 deletions src/action/macos/create_determinate_nix_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ pub struct CreateDeterminateNixVolume {
create_directory: StatefulAction<CreateDirectory>,
create_or_append_synthetic_conf: StatefulAction<CreateOrInsertIntoFile>,
create_synthetic_objects: StatefulAction<CreateSyntheticObjects>,
unmount_volume: StatefulAction<UnmountApfsVolume>,
create_volume: StatefulAction<CreateApfsVolume>,
pub(crate) unmount_volume: StatefulAction<UnmountApfsVolume>,
pub(crate) create_volume: StatefulAction<CreateApfsVolume>,
create_fstab_entry: StatefulAction<CreateFstabEntry>,
encrypt_volume: StatefulAction<EncryptApfsVolume>,
pub(crate) encrypt_volume: StatefulAction<EncryptApfsVolume>,
setup_volume_daemon: StatefulAction<CreateDeterminateVolumeService>,
bootstrap_volume: StatefulAction<BootstrapLaunchctlService>,
kickstart_launchctl_service: StatefulAction<KickstartLaunchctlService>,
Expand Down
12 changes: 6 additions & 6 deletions src/action/macos/create_nix_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwi

/// Create an APFS volume
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "create_apfs_volume")]
#[serde(tag = "action_name", rename = "create_nix_volume")]
pub struct CreateNixVolume {
disk: PathBuf,
name: String,
case_sensitive: bool,
encrypt: bool,
create_or_append_synthetic_conf: StatefulAction<CreateOrInsertIntoFile>,
create_synthetic_objects: StatefulAction<CreateSyntheticObjects>,
unmount_volume: StatefulAction<UnmountApfsVolume>,
create_volume: StatefulAction<CreateApfsVolume>,
pub(crate) unmount_volume: StatefulAction<UnmountApfsVolume>,
pub(crate) create_volume: StatefulAction<CreateApfsVolume>,
create_fstab_entry: StatefulAction<CreateFstabEntry>,
encrypt_volume: Option<StatefulAction<EncryptApfsVolume>>,
pub(crate) encrypt_volume: Option<StatefulAction<EncryptApfsVolume>>,
setup_volume_daemon: StatefulAction<CreateVolumeService>,
bootstrap_volume: StatefulAction<BootstrapLaunchctlService>,
kickstart_launchctl_service: StatefulAction<KickstartLaunchctlService>,
Expand Down Expand Up @@ -121,7 +121,7 @@ impl CreateNixVolume {
}

#[async_trait::async_trait]
#[typetag::serde(name = "create_apfs_volume")]
#[typetag::serde(name = "create_nix_volume")]
impl Action for CreateNixVolume {
fn action_tag() -> ActionTag {
ActionTag("create_nix_volume")
Expand All @@ -138,7 +138,7 @@ impl Action for CreateNixVolume {
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"create_apfs_volume",
"create_nix_volume",
disk = tracing::field::display(self.disk.display()),
name = self.name
)
Expand Down
2 changes: 2 additions & 0 deletions src/action/macos/create_volume_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ impl CreateVolumeService {
?expected_plist,
"Parsed plists not equal"
);
// TODO(cole-h): should this really be an error? we could just replace the contents.......
// maybe it's possible to have duplicate volume names...?
return Err(Self::error(CreateVolumeServiceError::DifferentPlist {
expected: expected_plist,
discovered: discovered_plist,
Expand Down
4 changes: 2 additions & 2 deletions src/action/macos/encrypt_apfs_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use super::CreateApfsVolume;
Encrypt an APFS volume
*/
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "encrypt_volume")]
#[serde(tag = "action_name", rename = "encrypt_apfs_volume")]
pub struct EncryptApfsVolume {
determinate_nix: bool,
disk: PathBuf,
Expand Down Expand Up @@ -124,7 +124,7 @@ impl EncryptApfsVolume {
}

#[async_trait::async_trait]
#[typetag::serde(name = "encrypt_volume")]
#[typetag::serde(name = "encrypt_apfs_volume")]
impl Action for EncryptApfsVolume {
fn action_tag() -> ActionTag {
ActionTag("encrypt_apfs_volume")
Expand Down
4 changes: 2 additions & 2 deletions src/action/macos/unmount_apfs_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::os::darwin::DiskUtilInfoOutput;
Unmount an APFS volume
*/
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(tag = "action_name", rename = "unmount_volume")]
#[serde(tag = "action_name", rename = "unmount_apfs_volume")]
pub struct UnmountApfsVolume {
disk: PathBuf,
name: String,
Expand All @@ -32,7 +32,7 @@ impl UnmountApfsVolume {
}

#[async_trait::async_trait]
#[typetag::serde(name = "unmount_volume")]
#[typetag::serde(name = "unmount_apfs_volume")]
impl Action for UnmountApfsVolume {
fn action_tag() -> ActionTag {
ActionTag("unmount_apfs_volume")
Expand Down
1 change: 1 addition & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl CommandExecute for NixInstallerCli {
NixInstallerSubcommand::Install(install) => install.execute().await,
NixInstallerSubcommand::Repair(repair) => repair.execute().await,
NixInstallerSubcommand::Uninstall(revert) => revert.execute().await,
NixInstallerSubcommand::SplitReceipt(split_receipt) => split_receipt.execute().await,
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion src/cli/subcommand/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use crate::{
cli::{
ensure_root,
interaction::{self, PromptChoice},
signal_channel, CommandExecute,
signal_channel,
subcommand::split_receipt::{PHASE1_RECEIPT_LOCATION, PHASE2_RECEIPT_LOCATION},
CommandExecute,
},
error::HasExpectedErrors,
plan::RECEIPT_LOCATION,
Expand Down Expand Up @@ -319,6 +321,20 @@ impl CommandExecute for Install {
copy_self_to_nix_dir()
.await
.wrap_err("Copying `nix-installer` to `/nix/nix-installer`")?;

if Path::new(PHASE1_RECEIPT_LOCATION).exists() {
tracing::debug!("Removing pre-existing uninstall phase 1 receipt at {PHASE1_RECEIPT_LOCATION} after successful install");
tokio::fs::remove_file(PHASE1_RECEIPT_LOCATION)
.await
.wrap_err_with(|| format!("Failed to remove uninstall phase 1 receipt at {PHASE1_RECEIPT_LOCATION}"))?;
}
if Path::new(PHASE2_RECEIPT_LOCATION).exists() {
tracing::debug!("Removing pre-existing uninstall phase 2 receipt at {PHASE2_RECEIPT_LOCATION} after successful install");
tokio::fs::remove_file(PHASE2_RECEIPT_LOCATION)
.await
.wrap_err_with(|| format!("Failed to remove uninstall phase 2 receipt at {PHASE2_RECEIPT_LOCATION}"))?;
}

println!(
"\
{success}\n\
Expand Down
16 changes: 10 additions & 6 deletions src/cli/subcommand/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
mod plan;
use plan::Plan;
mod install;
use install::Install;
mod plan;
mod repair;
use repair::Repair;
mod uninstall;
use uninstall::Uninstall;
mod self_test;
mod split_receipt;
mod uninstall;

use install::Install;
use plan::Plan;
use repair::Repair;
use self_test::SelfTest;
use split_receipt::SplitReceipt;
use uninstall::Uninstall;

#[allow(clippy::large_enum_variant)]
#[derive(Debug, clap::Subcommand)]
Expand All @@ -17,4 +20,5 @@ pub enum NixInstallerSubcommand {
Uninstall(Uninstall),
SelfTest(SelfTest),
Plan(Plan),
SplitReceipt(SplitReceipt),
}
Loading
Loading