forked from lix-project/lix-installer
Support for SteamOS Nix Offload in SteamOS 20230522.1000 (#495)
* Document how to handle different branches and buildIDs of the steam deck OS * Changes required for SteamOS 20230522.1000 * Speeling * Handle steamos upgrades better * Speeling * Tidy
This commit is contained in:
parent
539c21ec28
commit
5a07b2331b
|
@ -165,7 +165,7 @@ impl Action for CreateDirectory {
|
|||
None
|
||||
};
|
||||
|
||||
create_dir(path.clone())
|
||||
create_dir(&path)
|
||||
.await
|
||||
.map_err(|e| ActionErrorKind::CreateDirectory(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
|
102
src/action/linux/ensure_steamos_nix_directory.rs
Normal file
102
src/action/linux/ensure_steamos_nix_directory.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tokio::fs::create_dir;
|
||||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||
|
||||
/**
|
||||
Ensure SeamOS's `/nix` folder exists.
|
||||
|
||||
In SteamOS build ID 20230522.1000 (and, presumably, later) a `/nix` directory and related units
|
||||
exist. In previous versions of `nix-installer` the uninstall process would remove that directory.
|
||||
This action ensures that the folder does indeed exist.
|
||||
*/
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct EnsureSteamosNixDirectory;
|
||||
|
||||
impl EnsureSteamosNixDirectory {
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
|
||||
if which::which("steamos-readonly").is_err() {
|
||||
return Err(Self::error(ActionErrorKind::MissingSteamosBinary(
|
||||
"steamos-readonly".into(),
|
||||
)));
|
||||
}
|
||||
if Path::new("/nix").exists() {
|
||||
Ok(StatefulAction::completed(EnsureSteamosNixDirectory))
|
||||
} else {
|
||||
Ok(StatefulAction::uncompleted(EnsureSteamosNixDirectory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[typetag::serde(name = "ensure_steamos_nix_directory")]
|
||||
impl Action for EnsureSteamosNixDirectory {
|
||||
fn action_tag() -> ActionTag {
|
||||
ActionTag("ensure_steamos_nix_directory")
|
||||
}
|
||||
fn tracing_synopsis(&self) -> String {
|
||||
format!("Ensure SteamOS's `/nix` directory exists")
|
||||
}
|
||||
|
||||
fn tracing_span(&self) -> Span {
|
||||
span!(tracing::Level::DEBUG, "ensure_steamos_nix_directory",)
|
||||
}
|
||||
|
||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||
vec![ActionDescription::new(
|
||||
self.tracing_synopsis(),
|
||||
vec![
|
||||
"On more recent versions of SteamOS, a `/nix` folder now exists on the base image.".to_string(),
|
||||
"Previously, `nix-installer` created this directory through systemd units.".to_string(),
|
||||
"It's likely you updated SteamOS, then ran `/nix/nix-installer uninstall`, which deleted the `/nix` directory.".to_string(),
|
||||
],
|
||||
)]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
execute_command(
|
||||
Command::new("steamos-readonly")
|
||||
.process_group(0)
|
||||
.arg("disable")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let path = PathBuf::from("/nix");
|
||||
create_dir(&path)
|
||||
.await
|
||||
.map_err(|e| ActionErrorKind::CreateDirectory(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
execute_command(
|
||||
Command::new("steamos-readonly")
|
||||
.process_group(0)
|
||||
.arg("enable")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
// noop
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
pub(crate) mod ensure_steamos_nix_directory;
|
||||
pub(crate) mod provision_selinux;
|
||||
pub(crate) mod start_systemd_unit;
|
||||
pub(crate) mod systemctl_daemon_reload;
|
||||
|
||||
pub use ensure_steamos_nix_directory::EnsureSteamosNixDirectory;
|
||||
pub use provision_selinux::ProvisionSelinux;
|
||||
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError};
|
||||
pub use systemctl_daemon_reload::SystemctlDaemonReload;
|
||||
|
|
|
@ -54,7 +54,7 @@ impl Action for StartSystemdUnit {
|
|||
ActionTag("start_systemd_unit")
|
||||
}
|
||||
fn tracing_synopsis(&self) -> String {
|
||||
format!("Enable (and start) the systemd unit {}", self.unit)
|
||||
format!("Enable (and start) the systemd unit `{}`", self.unit)
|
||||
}
|
||||
|
||||
fn tracing_span(&self) -> Span {
|
||||
|
@ -106,7 +106,7 @@ impl Action for StartSystemdUnit {
|
|||
|
||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||
vec![ActionDescription::new(
|
||||
format!("Disable (and stop) the systemd unit {}", self.unit),
|
||||
format!("Disable (and stop) the systemd unit `{}`", self.unit),
|
||||
vec![],
|
||||
)]
|
||||
}
|
||||
|
|
81
src/action/linux/systemctl_daemon_reload.rs
Normal file
81
src/action/linux/systemctl_daemon_reload.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::path::Path;
|
||||
|
||||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||
|
||||
/**
|
||||
Run `systemctl daemon-reload` (on both execute and revert)
|
||||
*/
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct SystemctlDaemonReload;
|
||||
|
||||
impl SystemctlDaemonReload {
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
|
||||
if !Path::new("/run/systemd/system").exists() {
|
||||
return Err(Self::error(ActionErrorKind::SystemdMissing));
|
||||
}
|
||||
|
||||
if which::which("systemctl").is_err() {
|
||||
return Err(Self::error(ActionErrorKind::SystemdMissing));
|
||||
}
|
||||
|
||||
Ok(StatefulAction::uncompleted(SystemctlDaemonReload))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[typetag::serde(name = "systemctl_daemon_reload")]
|
||||
impl Action for SystemctlDaemonReload {
|
||||
fn action_tag() -> ActionTag {
|
||||
ActionTag("systemctl_daemon_reload")
|
||||
}
|
||||
fn tracing_synopsis(&self) -> String {
|
||||
format!("Run `systemctl daemon-reload`")
|
||||
}
|
||||
|
||||
fn tracing_span(&self) -> Span {
|
||||
span!(tracing::Level::DEBUG, "systemctl_daemon_reload",)
|
||||
}
|
||||
|
||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.arg("daemon-reload")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.arg("daemon-reload")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -243,7 +243,7 @@ pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
|
|||
///
|
||||
/// If this action calls sub-[`Action`]s, care should be taken to call [`try_revert`][StatefulAction::try_revert], not [`revert`][Action::revert], so that [`ActionState`] is handled correctly and tracing is done.
|
||||
///
|
||||
/// /// This is called by [`InstallPlan::uninstall`](crate::InstallPlan::uninstall) through [`StatefulAction::try_revert`] which handles tracing as well as if the action needs to revert based on its `action_state`.
|
||||
/// This is called by [`InstallPlan::uninstall`](crate::InstallPlan::uninstall) through [`StatefulAction::try_revert`] which handles tracing as well as if the action needs to revert based on its `action_state`.
|
||||
async fn revert(&mut self) -> Result<(), ActionError>;
|
||||
|
||||
fn stateful(self) -> StatefulAction<Self>
|
||||
|
@ -518,6 +518,8 @@ pub enum ActionErrorKind {
|
|||
Plist(#[from] plist::Error),
|
||||
#[error("Unexpected binary tarball contents found, the build result from `https://releases.nixos.org/?prefix=nix/` or `nix build nix#hydraJobs.binaryTarball.$SYSTEM` is expected")]
|
||||
MalformedBinaryTarball,
|
||||
#[error("Could not find `{0}` in PATH; This action only works on SteamOS, which should have this present in PATH.")]
|
||||
MissingSteamosBinary(String),
|
||||
#[error(
|
||||
"Could not find a supported command to create users in PATH; please install `useradd` or `adduser`"
|
||||
)]
|
||||
|
|
|
@ -381,6 +381,9 @@ pub enum PlannerError {
|
|||
NixExists,
|
||||
#[error("WSL1 is not supported, please upgrade to WSL2: https://learn.microsoft.com/en-us/windows/wsl/install#upgrade-version-from-wsl-1-to-wsl-2")]
|
||||
Wsl1,
|
||||
/// Failed to execute command
|
||||
#[error("Failed to execute command `{0}`")]
|
||||
Command(String, #[source] std::io::Error),
|
||||
#[cfg(feature = "diagnostics")]
|
||||
#[error(transparent)]
|
||||
Diagnostic(#[from] crate::diagnostics::DiagnosticError),
|
||||
|
@ -407,6 +410,7 @@ impl HasExpectedErrors for PlannerError {
|
|||
this @ PlannerError::NixOs => Some(Box::new(this)),
|
||||
this @ PlannerError::NixExists => Some(Box::new(this)),
|
||||
this @ PlannerError::Wsl1 => Some(Box::new(this)),
|
||||
PlannerError::Command(_, _) => None,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
PlannerError::Diagnostic(diagnostic_error) => Some(Box::new(diagnostic_error)),
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ One time step:
|
|||
6. Run `sudo steamos-chroot --disk /dev/nvme0n1 --partset B` and inside run the same above commands
|
||||
7. Safely turn off the VM!
|
||||
|
||||
|
||||
Repeated step:
|
||||
1. Create a snapshot of the base install to work on
|
||||
```sh
|
||||
|
@ -58,14 +59,52 @@ Repeated step:
|
|||
```
|
||||
3. **Do your testing!** You can `ssh deck@localhost -p 2222` in and use `rsync -e 'ssh -p 2222' result/bin/nix-installer deck@localhost:nix-installer` to send a `nix-installer build.
|
||||
4. Delete `steamos-hack.qcow2`
|
||||
|
||||
|
||||
To test a specific channel of the Steam Deck:
|
||||
1. Use `steamos-select-branch -l` to list possible branches.
|
||||
2. Run `steamos-select-branch $BRANCH` to choose a branch
|
||||
3. Run `steamos-update`
|
||||
4. Run `sudo steamos-chroot --disk /dev/vda --partset A` and inside run this
|
||||
```sh
|
||||
steamos-readonly disable
|
||||
echo -e '[Autologin]\nSession=plasma.desktop' > /etc/sddm.conf.d/zz-steamos-autologin.conf
|
||||
passwd deck
|
||||
sudo systemctl enable sshd
|
||||
steamos-readonly enable
|
||||
exit
|
||||
```
|
||||
5. Run `sudo steamos-chroot --disk /dev/vda --partset B` and inside run the same above commands
|
||||
6. Safely turn off the VM!
|
||||
|
||||
|
||||
To test on a specific build id of the Steam Deck:
|
||||
1. Determine the build id to be targeted. On a running system this is found in `/etc/os-release` under `BUILD_ID`.
|
||||
2. Run `steamos-update-os now --update-version $BUILD_ID`
|
||||
+ If you can't access a specific build ID you may need to change branches, see above.
|
||||
+ Be patient, don't ctrl+C it, it breaks. Don't reboot yet!
|
||||
4. Run `sudo steamos-chroot --disk /dev/vda --partset A` and inside run this
|
||||
```sh
|
||||
steamos-readonly disable
|
||||
echo -e '[Autologin]\nSession=plasma.desktop' > /etc/sddm.conf.d/zz-steamos-autologin.conf
|
||||
passwd deck
|
||||
sudo systemctl enable sshd
|
||||
steamos-readonly enable
|
||||
exit
|
||||
```
|
||||
5. Run `sudo steamos-chroot --disk /dev/vda --partset B` and inside run the same above commands
|
||||
6. Safely turn off the VM!
|
||||
|
||||
*/
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::{collections::HashMap, path::PathBuf, process::Output};
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::{
|
||||
action::{
|
||||
base::{CreateDirectory, CreateFile, RemoveDirectory},
|
||||
common::{ConfigureInitService, ConfigureNix, ProvisionNix},
|
||||
linux::StartSystemdUnit,
|
||||
linux::{EnsureSteamosNixDirectory, StartSystemdUnit, SystemctlDaemonReload},
|
||||
Action, StatefulAction,
|
||||
},
|
||||
planner::{Planner, PlannerError},
|
||||
|
@ -78,6 +117,7 @@ use super::ShellProfileLocations;
|
|||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
||||
pub struct SteamDeck {
|
||||
/// Where `/nix` will be bind mounted to. Deprecated in SteamOS build ID 20230522.1000 or later
|
||||
#[cfg_attr(
|
||||
feature = "cli",
|
||||
clap(
|
||||
|
@ -102,89 +142,127 @@ impl Planner for SteamDeck {
|
|||
}
|
||||
|
||||
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
|
||||
let persistence = &self.persistence;
|
||||
if !persistence.is_absolute() {
|
||||
return Err(PlannerError::Custom(Box::new(
|
||||
SteamDeckError::AbsolutePathRequired(self.persistence.clone()),
|
||||
)));
|
||||
};
|
||||
// Starting in roughly build ID `20230522.1000`, the Steam Deck has a `/home/.steamos/offload/nix` directory and `nix.mount` unit we can use instead of creating a mountpoint.
|
||||
let requires_nix_bind_mount = detect_requires_bind_mount().await?;
|
||||
|
||||
let nix_directory_buf = format!(
|
||||
"\
|
||||
[Unit]\n\
|
||||
Description=Create a `/nix` directory to be used for bind mounting\n\
|
||||
PropagatesStopTo=nix-daemon.service\n\
|
||||
PropagatesStopTo=nix.mount\n\
|
||||
DefaultDependencies=no\n\
|
||||
After=grub-recordfail.service\n\
|
||||
After=steamos-finish-oobe-migration.service\n\
|
||||
\n\
|
||||
[Service]\n\
|
||||
Type=oneshot\n\
|
||||
ExecStart=steamos-readonly disable\n\
|
||||
ExecStart=mkdir -vp /nix\n\
|
||||
ExecStart=chmod -v 0755 /nix\n\
|
||||
ExecStart=chown -v root /nix\n\
|
||||
ExecStart=chgrp -v root /nix\n\
|
||||
ExecStart=steamos-readonly enable\n\
|
||||
ExecStop=steamos-readonly disable\n\
|
||||
ExecStop=rmdir /nix\n\
|
||||
ExecStop=steamos-readonly enable\n\
|
||||
RemainAfterExit=true\n\
|
||||
"
|
||||
);
|
||||
let nix_directory_unit = CreateFile::plan(
|
||||
"/etc/systemd/system/nix-directory.service",
|
||||
None,
|
||||
None,
|
||||
0o0644,
|
||||
nix_directory_buf,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
let mut actions = vec![
|
||||
// Primarily for uninstall
|
||||
SystemctlDaemonReload::plan()
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
];
|
||||
|
||||
let create_bind_mount_buf = format!(
|
||||
"\
|
||||
[Unit]\n\
|
||||
Description=Mount `{persistence}` on `/nix`\n\
|
||||
PropagatesStopTo=nix-daemon.service\n\
|
||||
PropagatesStopTo=nix-directory.service\n\
|
||||
After=nix-directory.service\n\
|
||||
Requires=nix-directory.service\n\
|
||||
ConditionPathIsDirectory=/nix\n\
|
||||
DefaultDependencies=no\n\
|
||||
\n\
|
||||
[Mount]\n\
|
||||
What={persistence}\n\
|
||||
Where=/nix\n\
|
||||
Type=none\n\
|
||||
DirectoryMode=0755\n\
|
||||
Options=bind\n\
|
||||
\n\
|
||||
[Install]\n\
|
||||
RequiredBy=nix-daemon.service\n\
|
||||
RequiredBy=nix-daemon.socket\n
|
||||
",
|
||||
persistence = persistence.display(),
|
||||
);
|
||||
let create_bind_mount_unit = CreateFile::plan(
|
||||
"/etc/systemd/system/nix.mount",
|
||||
None,
|
||||
None,
|
||||
0o0644,
|
||||
create_bind_mount_buf,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
if let Ok(nix_mount_status) = systemctl_status("nix.mount").await {
|
||||
let nix_mount_status_stderr = String::from_utf8(nix_mount_status.stderr)?;
|
||||
if nix_mount_status_stderr.contains("Warning: The unit file, source configuration file or drop-ins of nix.mount changed on disk. Run 'systemctl daemon-reload' to reload units.") {
|
||||
return Err(PlannerError::Custom(Box::new(
|
||||
SteamDeckError::NixMountSystemctlDaemonReloadRequired,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
if requires_nix_bind_mount {
|
||||
let persistence = &self.persistence;
|
||||
if !persistence.is_absolute() {
|
||||
return Err(PlannerError::Custom(Box::new(
|
||||
SteamDeckError::AbsolutePathRequired(self.persistence.clone()),
|
||||
)));
|
||||
};
|
||||
actions.push(
|
||||
CreateDirectory::plan(&persistence, None, None, 0o0755, true)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
let nix_directory_buf = format!(
|
||||
"\
|
||||
[Unit]\n\
|
||||
Description=Create a `/nix` directory to be used for bind mounting\n\
|
||||
PropagatesStopTo=nix-daemon.service\n\
|
||||
PropagatesStopTo=nix.mount\n\
|
||||
DefaultDependencies=no\n\
|
||||
After=grub-recordfail.service\n\
|
||||
After=steamos-finish-oobe-migration.service\n\
|
||||
\n\
|
||||
[Service]\n\
|
||||
Type=oneshot\n\
|
||||
ExecStart=steamos-readonly disable\n\
|
||||
ExecStart=mkdir -vp /nix\n\
|
||||
ExecStart=chmod -v 0755 /nix\n\
|
||||
ExecStart=chown -v root /nix\n\
|
||||
ExecStart=chgrp -v root /nix\n\
|
||||
ExecStart=steamos-readonly enable\n\
|
||||
ExecStop=steamos-readonly disable\n\
|
||||
ExecStop=rmdir /nix\n\
|
||||
ExecStop=steamos-readonly enable\n\
|
||||
RemainAfterExit=true\n\
|
||||
"
|
||||
);
|
||||
let nix_directory_unit = CreateFile::plan(
|
||||
"/etc/systemd/system/nix-directory.service",
|
||||
None,
|
||||
None,
|
||||
0o0644,
|
||||
nix_directory_buf,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
actions.push(nix_directory_unit.boxed());
|
||||
|
||||
let create_bind_mount_buf = format!(
|
||||
"\
|
||||
[Unit]\n\
|
||||
Description=Mount `{persistence}` on `/nix`\n\
|
||||
PropagatesStopTo=nix-daemon.service\n\
|
||||
PropagatesStopTo=nix-directory.service\n\
|
||||
After=nix-directory.service\n\
|
||||
Requires=nix-directory.service\n\
|
||||
ConditionPathIsDirectory=/nix\n\
|
||||
DefaultDependencies=no\n\
|
||||
\n\
|
||||
[Mount]\n\
|
||||
What={persistence}\n\
|
||||
Where=/nix\n\
|
||||
Type=none\n\
|
||||
DirectoryMode=0755\n\
|
||||
Options=bind\n\
|
||||
\n\
|
||||
[Install]\n\
|
||||
RequiredBy=nix-daemon.service\n\
|
||||
RequiredBy=nix-daemon.socket\n
|
||||
",
|
||||
persistence = persistence.display(),
|
||||
);
|
||||
let create_bind_mount_unit = CreateFile::plan(
|
||||
"/etc/systemd/system/nix.mount",
|
||||
None,
|
||||
None,
|
||||
0o0644,
|
||||
create_bind_mount_buf,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
actions.push(create_bind_mount_unit.boxed());
|
||||
} else {
|
||||
let ensure_steamos_nix_directory = EnsureSteamosNixDirectory::plan()
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
actions.push(ensure_steamos_nix_directory.boxed());
|
||||
let start_nix_mount = StartSystemdUnit::plan("nix.mount".to_string(), true)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
actions.push(start_nix_mount.boxed());
|
||||
}
|
||||
|
||||
let ensure_symlinked_units_resolve_buf = format!(
|
||||
"\
|
||||
[Unit]\n\
|
||||
Description=Ensure Nix related units which are symlinked resolve\n\
|
||||
After=nix.mount\n\
|
||||
Requires=nix-directory.service\n\
|
||||
Requires=nix.mount\n\
|
||||
DefaultDependencies=no\n\
|
||||
\n\
|
||||
|
@ -208,6 +286,7 @@ impl Planner for SteamDeck {
|
|||
)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?;
|
||||
actions.push(ensure_symlinked_units_resolve_unit.boxed());
|
||||
|
||||
// We need to remove this path since it's part of the read-only install.
|
||||
let mut shell_profile_locations = ShellProfileLocations::default();
|
||||
|
@ -223,18 +302,16 @@ impl Planner for SteamDeck {
|
|||
.remove(index);
|
||||
}
|
||||
|
||||
Ok(vec![
|
||||
CreateDirectory::plan(&persistence, None, None, 0o0755, true)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
nix_directory_unit.boxed(),
|
||||
create_bind_mount_unit.boxed(),
|
||||
ensure_symlinked_units_resolve_unit.boxed(),
|
||||
StartSystemdUnit::plan("nix.mount".to_string(), false)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
if requires_nix_bind_mount {
|
||||
actions.push(
|
||||
StartSystemdUnit::plan("nix.mount".to_string(), false)
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
)
|
||||
}
|
||||
|
||||
actions.append(&mut vec![
|
||||
ProvisionNix::plan(&self.settings.clone())
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
|
@ -260,7 +337,12 @@ impl Planner for SteamDeck {
|
|||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
])
|
||||
SystemctlDaemonReload::plan()
|
||||
.await
|
||||
.map_err(PlannerError::Action)?
|
||||
.boxed(),
|
||||
]);
|
||||
Ok(actions)
|
||||
}
|
||||
|
||||
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||
|
@ -320,4 +402,36 @@ impl Into<BuiltinPlanner> for SteamDeck {
|
|||
pub enum SteamDeckError {
|
||||
#[error("`{0}` is not a path that can be canonicalized into an absolute path, bind mounts require an absolute path")]
|
||||
AbsolutePathRequired(PathBuf),
|
||||
#[error("A `/home/.steamos/offload/nix` exists, however `nix.mount` does not point at it. If Nix was previously installed, try uninstalling then rebooting first")]
|
||||
OffloadExistsButUnitIncorrect,
|
||||
#[error("Detected the SteamOS `nix.mount` unit exists, but `systemctl status nix.mount` did not return success. Try running `systemctl daemon-reload`.")]
|
||||
SteamosNixMountUnitNotExists,
|
||||
#[error("Detected the SteamOS `nix.mount` unit exists, but `systemctl status nix.mount` returned a warning that `systemctl daemon-reload` should be run. Run `systemctl daemon-reload` then `systemctl start nix.mount`, then try again.")]
|
||||
NixMountSystemctlDaemonReloadRequired,
|
||||
}
|
||||
|
||||
pub(crate) async fn detect_requires_bind_mount() -> Result<bool, PlannerError> {
|
||||
let steamos_nix_mount_unit_path = "/usr/lib/systemd/system/nix.mount";
|
||||
let nix_mount_unit = tokio::fs::read_to_string(steamos_nix_mount_unit_path)
|
||||
.await
|
||||
.map(|v| Some(v))
|
||||
.unwrap_or_else(|_| None);
|
||||
|
||||
match nix_mount_unit {
|
||||
Some(nix_mount_unit) if nix_mount_unit.contains("What=/home/.steamos/offload/nix") => {
|
||||
Ok(false)
|
||||
},
|
||||
None | Some(_) => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
async fn systemctl_status(unit: &str) -> Result<Output, PlannerError> {
|
||||
let mut command = Command::new("systemctl");
|
||||
command.arg("status");
|
||||
command.arg(unit);
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| PlannerError::Command(format!("{:?}", command.as_std()), e))?;
|
||||
Ok(output)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue