Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 4 additions & 11 deletions 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
Expand Up @@ -5,7 +5,7 @@ edition = "2021"

[dependencies]
clap = { version = "4.4.7", features = ["derive"] }
cmd_lib = "1.6.0"
cmd_lib = "1.9.3"
color-eyre = "0.6.2"
envfile = "0.2.1"
eyre = "0.6.8"
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ cargo build --release
sudo ./target/release/hibernation-control enable
```

## Steam Deck

You will need a manual step to get it working with the Steam Deck. Add `resume` to `HOOKS` at `/etc/mkinitcpio.conf`

Then you can execute:

```sh
sudo ./hibernate-control enable -s /path-to-your-swapfile
```

If don't want the new swapfile to be used for swap, you can run:

_TODO_ set priority
_TODO_ persist swap configuration (fstab or a systemd service)
_TODO_ How do I add it to the menu?

```sh
sudo ./hibernate-control enable -s /path-to-your-swapfile --swapfile-size 16_000_000
```

## Features

- Set up a swapfile of appropriate size (2 time the RAM)
Expand Down
67 changes: 42 additions & 25 deletions src/commands/enable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,36 @@ use sys_info::mem_info;
use crate::grub;
use crate::systemd;

pub fn run() -> eyre::Result<()> {
pub struct SwapOptions {
pub size: Option<u64>,
pub path: Option<String>,
}

pub struct Options {
pub swap: SwapOptions,
}

/// Enable hibernation on the system
pub fn run(options: Options) -> eyre::Result<()> {
sanity_check()?;

run_cmd!(info "Enabling hibernation")?;
backup_files()?;

// create_swapfile()?;
let swapfile_path = create_swapfile(&options.swap)?;

let uuid = get_uuid()?;
run_cmd!(info /swapfile uuid=$uuid)?;
let uuid = get_uuid(&swapfile_path)?;
run_cmd!(info ${swapfile_path} uuid=$uuid)?;

let offset = get_offset()?;
run_cmd!(info /swapfile offset=$offset)?;
let offset = get_offset(&swapfile_path)?;
run_cmd!(info ${swapfile_path} offset=$offset)?;

set_grub_options(uuid.clone(), offset)?;
set_initramfs_options(uuid, offset)?;

systemd::install()?;
// DONT NEED THESE TWO FOR THE STEAM DECK
// TODO add options to skip them
// set_initramfs_options(uuid, offset)?;
// systemd::install()?;

run_cmd!(
info Done;
Expand All @@ -51,8 +63,9 @@ pub fn sanity_check() -> eyre::Result<()> {
"swapon",
"findmnt",
"filefrag",
"grub-mkconfig",
"update-initramfs",
"update-grub",
// "grub-mkconfig",
// "update-initramfs",
] {
let test_command = run_fun!(which ${i};);
if test_command.is_err() {
Expand All @@ -74,37 +87,43 @@ pub fn backup_files() -> eyre::Result<()> {
Ok(())
}

pub fn create_swapfile() -> eyre::Result<()> {
pub fn create_swapfile(options: &SwapOptions) -> eyre::Result<String> {
let swapfile = options.path.clone().unwrap_or("/swapfile".to_string());
run_cmd!(
info "Creating swapfile (/swapfile)";
bash -c "swapoff /swapfile || true";
info "Creating swapfile (${swapfile})";
bash -c "swapoff ${swapfile} || true";
)?;

let forced_swap_size = options.size.clone();
let memory_size = mem_info()?.total;
let swap_size = memory_size * 2;
let swap_size = match forced_swap_size {
Some(size) => size,
None => swap_size,
};
let swap_size_mb = swap_size / 1024;
let swap_block_size_mb = 32;
let swap_num_blocks = swap_size_mb / swap_block_size_mb;

run_cmd!(
info "Allocating swapfile (${swap_size}MB)";
sudo dd if=/dev/zero of=/swapfile bs=${swap_block_size_mb}MB count=${swap_num_blocks};
chmod 600 /swapfile;
mkswap /swapfile;
swapon /swapfile;
sudo dd if=/dev/zero of=${swapfile} bs=${swap_block_size_mb}MB count=${swap_num_blocks};
chmod 600 ${swapfile};
mkswap ${swapfile};
swapon ${swapfile};
)?;
Ok(())
Ok(swapfile)
}

// TODO replace run_fun here
pub fn get_uuid() -> eyre::Result<String> {
let uuid = run_fun!(findmnt -no UUID -T /swapfile)?;
pub fn get_uuid(filepath: &str) -> eyre::Result<String> {
let uuid = run_fun!(findmnt -no UUID -T ${filepath})?;
Ok(uuid)
}

// TODO replace run_fun here
pub fn get_offset() -> eyre::Result<usize> {
let first_block = run_fun!(filefrag -v /swapfile | grep " 0:")?;
pub fn get_offset(filepath: &str) -> eyre::Result<usize> {
let first_block = run_fun!(filefrag -v ${filepath} | grep " 0:")?;
let first_block = first_block.replace(" ", "");
let first_block = first_block.replace("..", ":");
let block_iter = first_block.split(":");
Expand All @@ -118,10 +137,8 @@ pub fn set_grub_options(uuid: String, resume_offset: usize) -> eyre::Result<()>
grub::set_variable("resume".into(), format!("UUID={uuid}"))?;
grub::set_variable("resume_offset".into(), resume_offset.to_string())?;

// TODO use update-grub instead of grub-mkconfig
run_cmd!(
grub-mkconfig -o /boot/grub/grub.cfg;
grub-mkconfig -o /boot/efi/EFI/ubuntu/grub.cfg;
update-grub;
)?;
Ok(())
}
Expand Down
24 changes: 21 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::{Parser, Subcommand};
use hibernation_control::commands::enable::{Options, SwapOptions};

/// Manages Hibernation on Linux
#[derive(Parser)]
Expand All @@ -13,7 +14,15 @@ pub struct Cli {
enum Commands {
/// Create swap file, update fstab and grub, and enable hibernation on systemd
#[command()]
Enable,
Enable {
/// Size of the swap file in MiB
#[arg(long)]
swapfile_size: Option<u64>,

/// Path to the swap file
#[arg(short, long)]
swapfile_path: Option<String>,
},

/// This command DOES NOT remove the current swap file
#[command()]
Expand All @@ -23,8 +32,17 @@ enum Commands {
fn main() -> eyre::Result<()> {
color_eyre::install()?;
match Cli::parse().command {
Commands::Enable => {
hibernation_control::commands::enable::run()?;
Commands::Enable {
swapfile_size,
swapfile_path,
} => {
let options = Options {
swap: SwapOptions {
size: swapfile_size,
path: swapfile_path,
},
};
hibernation_control::commands::enable::run(options)?;
}
Commands::Disable => eyre::bail!("TODO"),
};
Expand Down