Skip to content

Commit

Permalink
Support VM export
Browse files Browse the repository at this point in the history
  • Loading branch information
htngr committed Feb 3, 2025
1 parent 1eb0761 commit 1293f64
Show file tree
Hide file tree
Showing 10 changed files with 380 additions and 170 deletions.
2 changes: 1 addition & 1 deletion codchi/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ See the following docs on how to register the completions with your shell:
#[clap(hide = true)]
Tray {},

#[clap(about = "Export the file system of a code machine.")]
#[clap(about = "Export the file system of a code machine including NixOS configuration and codchi secrets.")]
Tar {
/// Name of the code machine
name: String,
Expand Down
13 changes: 7 additions & 6 deletions codchi/src/config/flake.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;
use crate::cli::ModuleAttrPath;
use crate::consts::{store, ToPath};
use crate::consts::ToPath;
use crate::util::*;
use git_url_parse::{GitUrl, Scheme};
use std::marker::PhantomData;
Expand Down Expand Up @@ -44,7 +44,7 @@ fn metadata_to_query(metadata: &[(&str, Option<&String>)]) -> String {

impl<W: Hkd> FlakeUrl<W> {
/// Serialize to nix readable flake url, ignoring `flake_module` (the part after '#')
pub fn to_nix_url(&self, machine_name: &str) -> String {
pub fn to_nix_url(&self, local_root: LinuxPath) -> String {
use FlakeLocation::*;
use FlakeScheme::*;

Expand Down Expand Up @@ -75,7 +75,7 @@ impl<W: Hkd> FlakeUrl<W> {
}
}
Local { path } => {
let real_path = store::DIR_DATA.join_machine(machine_name).join_str(path).0;
let real_path = local_root.join_str(path).0;
let query = metadata_to_query(&[
("commit", self.commit.as_ref()),
("ref", self.r#ref.as_ref()),
Expand Down Expand Up @@ -302,6 +302,7 @@ pub enum FlakeScheme {

#[cfg(test)]
mod tests {
use crate::consts;
use super::*;

fn local() -> FlakeUrl<Required> {
Expand Down Expand Up @@ -357,15 +358,15 @@ mod tests {
#[test]
fn to_nix_url() {
assert_eq!(
local().to_nix_url("machine_name"),
local().to_nix_url(consts::store::DIR_DATA.join_machine("machine_name")),
"git+file:///data/machine/machine_name/docs/codchi?"
);
assert_eq!(
remote_gh().to_nix_url(""),
remote_gh().to_nix_url(LinuxPath("".to_string())),
"github:aformatik/codchi?host=github.com"
);
assert_eq!(
remote_custom().to_nix_url(""),
remote_custom().to_nix_url(LinuxPath("".to_string())),
"git+https://my:[email protected]/aformatik/codchi.git?commit=jakfkl2&ref=my-branch&host=foo.bar"
);
}
Expand Down
3 changes: 2 additions & 1 deletion codchi/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::cli::{InputOptions, ModuleAttrPath, ModuleName, NixpkgsLocation, Rela
use crate::config::git_url::{GitUrl, Scheme};
use crate::consts;
use crate::consts::user::DEFAULT_HOME;
use crate::consts::ToPath;
use crate::logging::{log_progress, set_progress_status};
use crate::platform::{nix::NixDriver, *};
use crate::progress_scope;
Expand Down Expand Up @@ -309,7 +310,7 @@ pub fn fetch_modules(
.unwrap_or_else(|| petname(1, "-").expect("Failed to generate random name"));

let flake_url = inquire_module_url(opts, url, allow_local)?;
let nix_url = flake_url.to_nix_url(&machine.name);
let nix_url = flake_url.to_nix_url(consts::store::DIR_DATA.join_machine(&machine.name));

let available_modules = progress_scope! {
set_progress_status("Fetching available modules...");
Expand Down
33 changes: 15 additions & 18 deletions codchi/src/platform/linux/lxd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,22 @@ use std::process::Command;
use std::sync::OnceLock;
use which::which;

static LXD_COMMAND: OnceLock<&str> = OnceLock::new();

pub fn init_lxc_command() -> Result<()> {
LXD_COMMAND.get_or_try_init(|| {
if let Ok(_) = which("lxd") {
log::trace!("Using LXD as container runtime.");
Ok("lxc")
} else if let Ok(_) = which("incus") {
log::trace!("Using Incus as container runtime.");
Ok("incus")
} else {
bail!("Either LXD or Incus is required to run Codchi.")
}
})?;
Ok(())
}

pub fn lxc_command(args: &[&str]) -> Command {
let mut cmd = Command::new(LXD_COMMAND.get().expect("init_lxc_command was not called"));
static LXD_COMMAND: OnceLock<&str> = OnceLock::new();
let command = LXD_COMMAND
.get_or_try_init(|| {
if let Ok(_) = which("lxd") {
log::trace!("Using LXD as container runtime.");
Ok("lxc")
} else if let Ok(_) = which("incus") {
log::trace!("Using Incus as container runtime.");
Ok("incus")
} else {
bail!("Either LXD or Incus is required to run Codchi.")
}
})
.unwrap();
let mut cmd = Command::new(command);
cmd.arg("-q");
cmd.args(args);
cmd
Expand Down
77 changes: 50 additions & 27 deletions codchi/src/platform/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,7 @@ use inquire::Confirm;
use log::*;
use lxd::lxc_command;
use std::{
collections::HashMap,
env,
fs::{self, File},
io::Write,
path::PathBuf,
process::Command,
sync::mpsc::channel,
thread,
collections::HashMap, env, fs, path::PathBuf, process::Command, sync::mpsc::channel, thread,
};

pub const NIX_STORE_PACKAGE: &str = "store-lxd";
Expand All @@ -34,7 +27,6 @@ pub struct StoreImpl {}

impl Store for StoreImpl {
fn start_or_init_container() -> Result<Self> {
lxd::init_lxc_command()?;
let status = lxd::container::get_platform_status(consts::CONTAINER_STORE_NAME).context(
"Failed to run LXD. It seems like LXD is not installed or set up correctly! \
Please see <https://codchi.dev/introduction/installation#linux> for setup instructions!",
Expand Down Expand Up @@ -232,23 +224,7 @@ impl MachineDriver for Machine {
)?;
}
with_tmp_file(&format!("codchi-{}-env", self.config.name), |path| {
let mut env = self.config.secrets.clone();

env.insert(
"DEBUG".to_string(),
if *DEBUG { "1" } else { "" }.to_string(),
);
env.insert("MACHINE_NAME".to_string(), self.config.name.clone());

let mut env_file = File::options()
.write(true)
.create(true)
.truncate(true)
.open(path)?;
for (key, value) in env {
writeln!(env_file, r#"export CODCHI_{key}="{value}""#)?;
}
env_file.sync_all()?;
self.write_env_file(&path)?;
lxd::container::file_push(
&machine_name(&self.config.name),
path,
Expand Down Expand Up @@ -380,14 +356,61 @@ Is it okay to use {sudo_name}? [y/n]",

with_tmp_file(&format!("codchi-backup-{}", self.config.name), |tmp_dir| {
fs::create_dir_all(tmp_dir)?;

let etc_nixos = tmp_dir.join("backup/container/rootfs/etc/nixos");
etc_nixos.get_or_create()?;
self.write_flake_standalone(etc_nixos.join("flake.nix"))?;
fs::copy(
consts::host::DIR_CONFIG
.join_machine(&self.config.name)
.join("flake.lock"),
etc_nixos.join("flake.lock"),
)
.trace_err("Failed copying flake.lock")
.ignore();

self.write_env_file(tmp_dir.join(format!(
"backup/container/rootfs{}",
consts::machine::CODCHI_ENV.0
)))?;

let lxc_export = tmp_dir.join("lxc_export.tar").display().to_string();
let target_file = target_file.display().to_string();
lxd::container::export(
&consts::machine::machine_name(&self.config.name),
&lxc_export,
)?;
Command::new("tar")
.args(["-C", &tmp_dir.display().to_string(), "-xf", &lxc_export])
.args([
"-C",
&tmp_dir.display().to_string(),
"-xf",
&lxc_export,
"--exclude=.files",
"--exclude=bin*",
"--exclude=dev*",
"--exclude=etc*",
"--exclude=lib*",
"--exclude=lib64*",
"--exclude=nix*",
"--exclude=proc*",
"--exclude=run*",
"--exclude=sbin*",
"--exclude=sys*",
"--exclude=tmp*",
"--exclude=var/.updated",
"--exclude=var/cache",
"--exclude=var/db*",
"--exclude=var/empty",
"--exclude=var/lib/nixos*",
"--exclude=var/lib/systemd*",
"--exclude=var/lock",
"--exclude=var/log*",
"--exclude=var/spool*",
])
.wait_ok()?;
Command::new("chmod")
.args(["755", &tmp_dir.display().to_string()])
.wait_ok()?;
with_suspended_progress(|| {
command_with_privileges(
Expand Down
93 changes: 89 additions & 4 deletions codchi/src/platform/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{
platform::HostImpl, Host, LinuxCommandBuilder, LinuxCommandTarget, LinuxUser, NixDriver,
};
use crate::{
cli::CODCHI_DRIVER_MODULE,
cli::{CODCHI_DRIVER_MODULE, DEBUG},
config::{ConfigResult, EnvSecret, FlakeLocation, MachineConfig},
consts::{self, host, ToPath},
logging::{hide_progress, log_progress, set_progress_status, with_suspended_progress},
Expand All @@ -13,7 +13,14 @@ use crate::{
use anyhow::{bail, Context, Result};
use itertools::Itertools;
use log::Level;
use std::{collections::HashMap, fs, sync::mpsc::channel, thread};
use std::{
collections::HashMap,
fs::{self, File},
io::Write,
path::Path,
sync::mpsc::channel,
thread,
};

pub trait MachineDriver: Sized {
fn cmd(&self) -> impl LinuxCommandTarget;
Expand Down Expand Up @@ -144,7 +151,7 @@ fi
.map(|(name, url)| {
format!(
r#" "{name}".url = "{}";"#,
url.to_nix_url(&self.config.name)
url.to_nix_url(consts::store::DIR_DATA.join_machine(&self.config.name))
)
})
.join("\n");
Expand Down Expand Up @@ -205,6 +212,85 @@ fi
Ok(())
}

pub fn write_flake_standalone<P: AsRef<Path>>(&self, target: P) -> Result<()> {
let flake = {
let codchi_url = consts::CODCHI_FLAKE_URL;
let codchi_driver = CODCHI_DRIVER_MODULE;
let module_inputs = self
.config
.modules
.iter()
.map(|(name, url)| {
format!(
r#" "{name}".url = "{}";"#,
url.to_nix_url(consts::user::DEFAULT_HOME.clone())
)
})
.join("\n");
let nix_system = consts::NIX_SYSTEM;
let nixpkgs = if let Some(name) = &self.config.nixpkgs_from {
format!(r#"inputs."{name}".inputs.nixpkgs"#)
} else {
"inputs.codchi_driver.inputs.nixpkgs".to_string()
};
let modules = self
.config
.modules
.iter()
.map(|(name, url)| {
format!(
r#" inputs."{name}".{module_name}"#,
module_name = url.flake_attr
)
})
.join("\n");
format!(
r#"{{
inputs = {{
{codchi_driver}.url = "{codchi_url}";
{module_inputs}
}};
outputs = inputs: {{
nixosConfigurations.nixos = {nixpkgs}.lib.nixosSystem {{
system = "{nix_system}";
modules = [
{{
_module.args.inputs.nixpkgs = {nixpkgs};
codchi.driver.name = "none";
}}
inputs.{codchi_driver}.nixosModules.default
{modules}
];
}};
}};
}}"#
)
};
fs::write(target, flake)?;
Ok(())
}

pub fn write_env_file<P: AsRef<Path>>(&self, target: P) -> Result<()> {
let mut env = self.config.secrets.clone();

env.insert(
"DEBUG".to_string(),
if *DEBUG { "1" } else { "" }.to_string(),
);
env.insert("MACHINE_NAME".to_string(), self.config.name.clone());

let mut env_file = File::options()
.write(true)
.create(true)
.truncate(true)
.open(target)?;
for (key, value) in env {
writeln!(env_file, r#"export CODCHI_{key}="{value}""#)?;
}
env_file.sync_all()?;
Ok(())
}

pub fn update_flake(&self) -> Result<()> {
Driver::store()
.cmd()
Expand Down Expand Up @@ -514,7 +600,6 @@ git add flake.*
ConfigResult::None => {}
}


let (lock, _) = MachineConfig::open(target_name, true)?;
let mut new_cfg = self.config.clone();
new_cfg.name = target_name.to_string();
Expand Down
Loading

0 comments on commit 1293f64

Please sign in to comment.