diff --git a/.github/workflows/product.yaml b/.github/workflows/product.yaml index 5d3df479e..702dbe28f 100644 --- a/.github/workflows/product.yaml +++ b/.github/workflows/product.yaml @@ -134,9 +134,9 @@ jobs: - name: Build image run: | - make V=1 embassyos-raspi.img --debug + make V=1 eos_raspberrypi-uninit.img --debug - uses: actions/upload-artifact@v3 with: name: image - path: embassyos-raspi.img + path: eos_raspberrypi-uninit.img diff --git a/Makefile b/Makefile index 12079b89d..5c5fdb45f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -RASPI_TARGETS := embassyos-raspi.img embassyos-raspi.tar.gz gzip lite-upgrade.img -ARCH := $(shell if echo $(RASPI_TARGETS) | grep -qw "$(MAKECMDGOALS)"; then echo aarch64; else uname -m; fi) +RASPI_TARGETS := eos_raspberrypi-uninit.img eos_raspberrypi-uninit.tar.gz OS_ARCH := $(shell if echo $(RASPI_TARGETS) | grep -qw "$(MAKECMDGOALS)"; then echo raspberrypi; else uname -m; fi) +ARCH := $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo aarch64; else echo $(OS_ARCH); fi) ENVIRONMENT_FILE = $(shell ./check-environment.sh) GIT_HASH_FILE = $(shell ./check-git-hash.sh) VERSION_FILE = $(shell ./check-version.sh) @@ -19,18 +19,28 @@ FRONTEND_DIAGNOSTIC_UI_SRC := $(shell find frontend/projects/diagnostic-ui) FRONTEND_INSTALL_WIZARD_SRC := $(shell find frontend/projects/install-wizard) PATCH_DB_CLIENT_SRC := $(shell find patch-db/client -not -path patch-db/client/dist) GZIP_BIN := $(shell which pigz || which gzip) -$(shell sudo true) +ALL_TARGETS := $(EMBASSY_BINS) system-images/compat/docker-images/aarch64.tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar $(EMBASSY_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) + +ifeq ($(REMOTE),) + mkdir = mkdir -p $1 + rm = rm -rf $@ + cp = cp -r $1 $2 +else + mkdir = ssh $(REMOTE) 'mkdir -p $1' + rm = ssh $(REMOTE) 'rm -rf $@' +define cp + tar --transform "s|^$1|x|" -czv -f- $1 | ssh $(REMOTE) "sudo tar --transform 's|^x|$2|' -xzv -f- -C /" +endef +endif .DELETE_ON_ERROR: -.PHONY: all gzip install clean format sdk snapshots frontends ui backend +.PHONY: all gzip install clean format sdk snapshots frontends ui backend reflash eos_raspberrypi.img sudo -all: $(EMBASSY_SRC) $(EMBASSY_BINS) system-images/compat/docker-images/aarch64.tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) +all: $(ALL_TARGETS) -gzip: embassyos-raspi.tar.gz - -embassyos-raspi.tar.gz: embassyos-raspi.img - tar --format=posix -cS -f- embassyos-raspi.img | $(GZIP_BIN) > embassyos-raspi.tar.gz +sudo: + sudo true clean: rm -f 2022-01-28-raspios-bullseye-arm64-lite.zip @@ -62,7 +72,7 @@ format: sdk: cd backend/ && ./install-sdk.sh -embassyos-raspi.img: all raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep +eos_raspberrypi-uninit.img: $(ALL_TARGETS) raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep | sudo ! test -f embassyos-raspi.img || rm embassyos-raspi.img ./build/raspberry-pi/make-image.sh @@ -70,41 +80,63 @@ lite-upgrade.img: raspios.img cargo-deps/aarch64-unknown-linux-gnu/release/nc-br ! test -f lite-upgrade.img || rm lite-upgrade.img ./build/raspberry-pi/make-upgrade-image.sh -eos_raspberrypi.img: raspios.img $(BUILD_SRC) eos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) +eos_raspberrypi.img: raspios.img $(BUILD_SRC) eos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) | sudo ! test -f eos_raspberrypi.img || rm eos_raspberrypi.img ./build/raspberry-pi/make-initialized-image.sh # For creating os images. DO NOT USE -install: all - mkdir -p $(DESTDIR)/usr/bin - cp backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-init $(DESTDIR)/usr/bin/ - cp backend/target/$(ARCH)-unknown-linux-gnu/release/embassyd $(DESTDIR)/usr/bin/ - cp backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-cli $(DESTDIR)/usr/bin/ - cp backend/target/$(ARCH)-unknown-linux-gnu/release/avahi-alias $(DESTDIR)/usr/bin/ +install: $(ALL_TARGETS) + $(call mkdir,$(DESTDIR)/usr/bin) + $(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-init,$(DESTDIR)/usr/bin/embassy-init) + $(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/embassyd,$(DESTDIR)/usr/bin/embassyd) + $(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/embassy-cli,$(DESTDIR)/usr/bin/embassy-cli) + $(call cp,backend/target/$(ARCH)-unknown-linux-gnu/release/avahi-alias,$(DESTDIR)/usr/bin/avahi-alias) - mkdir -p $(DESTDIR)/usr/lib - rm -rf $(DESTDIR)/usr/lib/embassy - cp -r build/lib $(DESTDIR)/usr/lib/embassy + $(call mkdir,$(DESTDIR)/usr/lib) + $(call rm,$(DESTDIR)/usr/lib/embassy) + $(call cp,build/lib,$(DESTDIR)/usr/lib/embassy) - cp ENVIRONMENT.txt $(DESTDIR)/usr/lib/embassy/ - cp GIT_HASH.txt $(DESTDIR)/usr/lib/embassy/ - cp VERSION.txt $(DESTDIR)/usr/lib/embassy/ + $(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/embassy/ENVIRONMENT.txt) + $(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/embassy/GIT_HASH.txt) + $(call cp,VERSION.txt,$(DESTDIR)/usr/lib/embassy/VERSION.txt) - mkdir -p $(DESTDIR)/usr/lib/embassy/container - cp libs/target/aarch64-unknown-linux-musl/release/embassy_container_init $(DESTDIR)/usr/lib/embassy/container/embassy_container_init.arm64 - cp libs/target/x86_64-unknown-linux-musl/release/embassy_container_init $(DESTDIR)/usr/lib/embassy/container/embassy_container_init.amd64 + $(call mkdir,$(DESTDIR)/usr/lib/embassy/container) + $(call cp,libs/target/aarch64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/embassy/container/embassy_container_init.arm64) + $(call cp,libs/target/x86_64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/embassy/container/embassy_container_init.amd64) - mkdir -p $(DESTDIR)/usr/lib/embassy/system-images - cp system-images/compat/docker-images/aarch64.tar $(DESTDIR)/usr/lib/embassy/system-images/compat.tar - cp system-images/utils/docker-images/$(ARCH).tar $(DESTDIR)/usr/lib/embassy/system-images/utils.tar - cp system-images/binfmt/docker-images/$(ARCH).tar $(DESTDIR)/usr/lib/embassy/system-images/binfmt.tar + $(call mkdir,$(DESTDIR)/usr/lib/embassy/system-images) + $(call cp,system-images/compat/docker-images/aarch64.tar,$(DESTDIR)/usr/lib/embassy/system-images/compat.tar) + $(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/embassy/system-images/utils.tar) + $(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/embassy/system-images/binfmt.tar) - mkdir -p $(DESTDIR)/var/www/html - cp -r frontend/dist/diagnostic-ui $(DESTDIR)/var/www/html/diagnostic - cp -r frontend/dist/setup-wizard $(DESTDIR)/var/www/html/setup - cp -r frontend/dist/install-wizard $(DESTDIR)/var/www/html/install - cp -r frontend/dist/ui $(DESTDIR)/var/www/html/main - cp index.html $(DESTDIR)/var/www/html/ + $(call mkdir,$(DESTDIR)/var/www/html) + $(call cp,frontend/dist/diagnostic-ui,$(DESTDIR)/var/www/html/diagnostic) + $(call cp,frontend/dist/setup-wizard,$(DESTDIR)/var/www/html/setup) + $(call cp,frontend/dist/install-wizard,$(DESTDIR)/var/www/html/install) + $(call cp,frontend/dist/ui,$(DESTDIR)/var/www/html/main) + $(call cp,index.html,$(DESTDIR)/var/www/html/index.html) + +update-overlay: + @echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m" + @echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m" + @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi + @if [ "`ssh $(REMOTE) 'cat /usr/lib/embassy/VERSION.txt'`" != "`cat ./VERSION.txt`" ]; then >&2 echo "Embassy requires migrations: update-overlay is unavailable." && false; fi + @if ssh $(REMOTE) "pidof embassy-init"; then >&2 echo "Embassy in INIT: update-overlay is unavailable." && false; fi + ssh $(REMOTE) "sudo systemctl stop embassyd" + $(MAKE) install REMOTE=$(REMOTE) OS_ARCH=$(OS_ARCH) + ssh $(REMOTE) "sudo systemctl start embassyd" + +update: + @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi + ssh $(REMOTE) "sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/" + $(MAKE) install REMOTE=$(REMOTE) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH) + ssh $(REMOTE) "sudo touch /media/embassy/config/upgrade && sudo sync && sudo reboot" + +emulate-reflash: + @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi + ssh $(REMOTE) "sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/" + $(MAKE) install REMOTE=$(REMOTE) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH) + ssh $(REMOTE) "sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot" system-images/compat/docker-images/aarch64.tar: $(COMPAT_SRC) cd system-images/compat && make diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 60eec36b2..10f9ace85 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -485,6 +485,12 @@ dependencies = [ "serde", ] +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + [[package]] name = "bumpalo" version = "3.12.0" @@ -770,6 +776,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + [[package]] name = "crc" version = "3.0.1" @@ -1369,6 +1384,7 @@ dependencies = [ "fd-lock-rs", "futures", "git-version", + "gpt", "helpers", "hex", "hmac 0.12.1", @@ -1439,7 +1455,7 @@ dependencies = [ "trust-dns-server", "typed-builder", "url", - "uuid", + "uuid 1.3.0", "zeroize", ] @@ -1882,6 +1898,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gpt" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd7365d734a70ac5dd7be791b0c96083852188df015b8c665bb2dadb108a743" +dependencies = [ + "bitflags", + "crc 1.8.1", + "log", + "uuid 0.8.2", +] + [[package]] name = "group" version = "0.12.1" @@ -4503,7 +4531,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc", + "crc 3.0.1", "crossbeam-queue", "dirs", "dotenvy", @@ -5757,6 +5785,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.8", +] + [[package]] name = "uuid" version = "1.3.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 972d76a13..de1d29a53 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -81,6 +81,7 @@ emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", fd-lock-rs = "0.1.4" futures = "0.3.21" git-version = "0.3.5" +gpt = "3.0.0" helpers = { path = "../libs/helpers" } embassy_container_init = { path = "../libs/embassy_container_init" } hex = "0.4.3" diff --git a/backend/src/context/install.rs b/backend/src/context/install.rs index dcc0aab65..c5ae8000e 100644 --- a/backend/src/context/install.rs +++ b/backend/src/context/install.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use tokio::sync::broadcast::Sender; use tracing::instrument; -use crate::net::net_utils::find_eth_iface; +use crate::net::utils::find_eth_iface; use crate::util::config::load_config_from_paths; use crate::Error; diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index b0fe6f6b5..ae017c699 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -19,7 +19,7 @@ use crate::account::AccountInfo; use crate::config::spec::{PackagePointerSpec, SystemPointerSpec}; use crate::install::progress::InstallProgress; use crate::net::interface::InterfaceId; -use crate::net::net_utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; +use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId}; use crate::status::health_check::HealthCheckId; use crate::status::Status; diff --git a/backend/src/disk/mod.rs b/backend/src/disk/mod.rs index c2eb8cbd8..517b2cc6c 100644 --- a/backend/src/disk/mod.rs +++ b/backend/src/disk/mod.rs @@ -18,15 +18,21 @@ pub mod util; pub const BOOT_RW_PATH: &str = "/media/boot-rw"; pub const REPAIR_DISK_PATH: &str = "/media/embassy/config/repair-disk"; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct OsPartitionInfo { + pub efi: Option, pub boot: PathBuf, pub root: PathBuf, } impl OsPartitionInfo { pub fn contains(&self, logicalname: impl AsRef) -> bool { - &*self.boot == logicalname.as_ref() || &*self.root == logicalname.as_ref() + self.efi + .as_ref() + .map(|p| p == logicalname.as_ref()) + .unwrap_or(false) + || &*self.boot == logicalname.as_ref() + || &*self.root == logicalname.as_ref() } } diff --git a/backend/src/disk/mount/filesystem/efivarfs.rs b/backend/src/disk/mount/filesystem/efivarfs.rs new file mode 100644 index 000000000..19a38e60f --- /dev/null +++ b/backend/src/disk/mount/filesystem/efivarfs.rs @@ -0,0 +1,40 @@ +use std::os::unix::ffi::OsStrExt; +use std::path::Path; + +use async_trait::async_trait; +use digest::generic_array::GenericArray; +use digest::{Digest, OutputSizeUser}; +use sha2::Sha256; + +use super::{FileSystem, MountType, ReadOnly}; +use crate::util::Invoke; +use crate::{Error, ResultExt}; + +pub struct EfiVarFs; +#[async_trait] +impl FileSystem for EfiVarFs { + async fn mount + Send + Sync>( + &self, + mountpoint: P, + mount_type: MountType, + ) -> Result<(), Error> { + tokio::fs::create_dir_all(mountpoint.as_ref()).await?; + let mut cmd = tokio::process::Command::new("mount"); + cmd.arg("-t") + .arg("efivarfs") + .arg("efivarfs") + .arg(mountpoint.as_ref()); + if mount_type == ReadOnly { + cmd.arg("-o").arg("ro"); + } + cmd.invoke(crate::ErrorKind::Filesystem).await?; + Ok(()) + } + async fn source_hash( + &self, + ) -> Result::OutputSize>, Error> { + let mut sha = Sha256::new(); + sha.update("EfiVarFs"); + Ok(sha.finalize()) + } +} diff --git a/backend/src/disk/mount/filesystem/mod.rs b/backend/src/disk/mount/filesystem/mod.rs index fd87051ea..00247e0dd 100644 --- a/backend/src/disk/mount/filesystem/mod.rs +++ b/backend/src/disk/mount/filesystem/mod.rs @@ -11,6 +11,7 @@ pub mod bind; pub mod block_dev; pub mod cifs; pub mod ecryptfs; +pub mod efivarfs; pub mod httpdirfs; pub mod label; diff --git a/backend/src/disk/mount/guard.rs b/backend/src/disk/mount/guard.rs index 18e93f6d7..3720a2125 100644 --- a/backend/src/disk/mount/guard.rs +++ b/backend/src/disk/mount/guard.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Weak}; use lazy_static::lazy_static; +use models::ResultExt; use tokio::sync::Mutex; use tracing::instrument; @@ -36,9 +37,21 @@ impl MountGuard { mounted: true, }) } - pub async fn unmount(mut self) -> Result<(), Error> { + pub async fn unmount(mut self, delete_mountpoint: bool) -> Result<(), Error> { if self.mounted { unmount(&self.mountpoint).await?; + if delete_mountpoint { + match tokio::fs::remove_dir(&self.mountpoint).await { + Err(e) if e.raw_os_error() == Some(39) => Ok(()), // directory not empty + a => a, + } + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("rm {}", self.mountpoint.display()), + ) + })?; + } self.mounted = false; } Ok(()) @@ -60,7 +73,7 @@ impl Drop for MountGuard { #[async_trait::async_trait] impl GenericMountGuard for MountGuard { async fn unmount(mut self) -> Result<(), Error> { - MountGuard::unmount(self).await + MountGuard::unmount(self, false).await } } @@ -111,7 +124,7 @@ impl TmpMountGuard { } pub async fn unmount(self) -> Result<(), Error> { if let Ok(guard) = Arc::try_unwrap(self.guard) { - guard.unmount().await?; + guard.unmount(true).await?; } Ok(()) } diff --git a/backend/src/disk/mount/util.rs b/backend/src/disk/mount/util.rs index f1194c53c..803aa3d90 100644 --- a/backend/src/disk/mount/util.rs +++ b/backend/src/disk/mount/util.rs @@ -48,15 +48,5 @@ pub async fn unmount>(mountpoint: P) -> Result<(), Error> { .arg(mountpoint.as_ref()) .invoke(crate::ErrorKind::Filesystem) .await?; - match tokio::fs::remove_dir(mountpoint.as_ref()).await { - Err(e) if e.raw_os_error() == Some(39) => Ok(()), // directory not empty - a => a, - } - .with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - format!("rm {}", mountpoint.as_ref().display()), - ) - })?; Ok(()) } diff --git a/backend/src/disk/util.rs b/backend/src/disk/util.rs index 97f5ae491..9c98c97c0 100644 --- a/backend/src/disk/util.rs +++ b/backend/src/disk/util.rs @@ -23,10 +23,18 @@ use crate::util::serde::IoFormat; use crate::util::{Invoke, Version}; use crate::{Error, ResultExt as _}; +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum PartitionTable { + Mbr, + Gpt, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct DiskInfo { pub logicalname: PathBuf, + pub partition_table: Option, pub vendor: Option, pub model: Option, pub partitions: Vec, @@ -61,6 +69,24 @@ lazy_static::lazy_static! { static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap(); } +#[instrument(skip(path))] +pub async fn get_partition_table>(path: P) -> Result, Error> { + Ok(String::from_utf8( + Command::new("fdisk") + .arg("-l") + .arg(path.as_ref()) + .invoke(crate::ErrorKind::BlockDevice) + .await?, + )? + .lines() + .find_map(|l| l.strip_prefix("Disklabel type:")) + .and_then(|t| match t.trim() { + "dos" => Some(PartitionTable::Mbr), + "gpt" => Some(PartitionTable::Gpt), + _ => None, + })) +} + #[instrument(skip(path))] pub async fn get_vendor>(path: P) -> Result, Error> { let vendor = tokio::fs::read_to_string( @@ -328,6 +354,16 @@ pub async fn list(os: &OsPartitionInfo) -> Result, Error> { } async fn disk_info(disk: PathBuf) -> DiskInfo { + let partition_table = get_partition_table(&disk) + .await + .map_err(|e| { + tracing::warn!( + "Could not get partition table of {}: {}", + disk.display(), + e.source + ) + }) + .unwrap_or_default(); let vendor = get_vendor(&disk) .await .map_err(|e| tracing::warn!("Could not get vendor of {}: {}", disk.display(), e.source)) @@ -342,6 +378,7 @@ async fn disk_info(disk: PathBuf) -> DiskInfo { .unwrap_or_default(); DiskInfo { logicalname: disk, + partition_table, vendor, model, partitions: Vec::new(), diff --git a/backend/src/fstab.template b/backend/src/fstab.template deleted file mode 100644 index 38f9ac1a9..000000000 --- a/backend/src/fstab.template +++ /dev/null @@ -1,2 +0,0 @@ -{boot} /boot vfat defaults 0 2 -{root} / ext4 defaults 0 1 \ No newline at end of file diff --git a/backend/src/init.rs b/backend/src/init.rs index 7c3e6a800..e1b7ed843 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -232,7 +232,12 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { format!("write {}", LOCAL_AUTH_COOKIE_PATH), ) })?; - tokio::fs::set_permissions(LOCAL_AUTH_COOKIE_PATH, Permissions::from_mode(046)).await?; + tokio::fs::set_permissions(LOCAL_AUTH_COOKIE_PATH, Permissions::from_mode(0o046)).await?; + Command::new("chown") + .arg("root:embassy") + .arg(LOCAL_AUTH_COOKIE_PATH) + .invoke(crate::ErrorKind::Filesystem) + .await?; } let secret_store = cfg.secret_store().await?; diff --git a/backend/src/middleware/auth.rs b/backend/src/middleware/auth.rs index 27b952fe7..ecc07ffd6 100644 --- a/backend/src/middleware/auth.rs +++ b/backend/src/middleware/auth.rs @@ -106,7 +106,7 @@ impl HasValidSession { } pub async fn from_local(local: &Cookie<'_>) -> Result { - let token = tokio::fs::read_to_string("/run/embassy/rpc.authcookie").await?; + let token = tokio::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH).await?; if local.get_value() == &*token { Ok(Self(())) } else { diff --git a/backend/src/net/dhcp.rs b/backend/src/net/dhcp.rs index 5d48145a8..ccf2b109f 100644 --- a/backend/src/net/dhcp.rs +++ b/backend/src/net/dhcp.rs @@ -7,7 +7,7 @@ use tokio::sync::RwLock; use crate::context::RpcContext; use crate::db::model::IpInfo; -use crate::net::net_utils::{iface_is_physical, list_interfaces}; +use crate::net::utils::{iface_is_physical, list_interfaces}; use crate::util::display_none; use crate::Error; diff --git a/backend/src/net/mod.rs b/backend/src/net/mod.rs index 795a5d2bd..1c6010662 100644 --- a/backend/src/net/mod.rs +++ b/backend/src/net/mod.rs @@ -13,11 +13,11 @@ pub mod keys; #[cfg(feature = "avahi")] pub mod mdns; pub mod net_controller; -pub mod net_utils; pub mod ssl; pub mod static_server; pub mod tor; -pub mod vhost_controller; +pub mod utils; +pub mod vhost; pub mod web_server; pub mod wifi; diff --git a/backend/src/net/net_controller.rs b/backend/src/net/net_controller.rs index 2bcd3c917..08c7fccb7 100644 --- a/backend/src/net/net_controller.rs +++ b/backend/src/net/net_controller.rs @@ -15,7 +15,7 @@ use crate::net::keys::Key; use crate::net::mdns::MdnsController; use crate::net::ssl::{export_cert, SslManager}; use crate::net::tor::TorController; -use crate::net::vhost_controller::VHostController; +use crate::net::vhost::VHostController; use crate::s9pk::manifest::PackageId; use crate::volume::cert_dir; use crate::{Error, HOST_IP}; diff --git a/backend/src/net/net_utils.rs b/backend/src/net/utils.rs similarity index 100% rename from backend/src/net/net_utils.rs rename to backend/src/net/utils.rs diff --git a/backend/src/net/vhost_controller.rs b/backend/src/net/vhost.rs similarity index 99% rename from backend/src/net/vhost_controller.rs rename to backend/src/net/vhost.rs index c8b35b307..473325909 100644 --- a/backend/src/net/vhost_controller.rs +++ b/backend/src/net/vhost.rs @@ -16,8 +16,8 @@ use tokio_rustls::rustls::{RootCertStore, ServerConfig}; use tokio_rustls::{LazyConfigAcceptor, TlsConnector}; use crate::net::keys::Key; -use crate::net::net_utils::SingleAccept; use crate::net::ssl::SslManager; +use crate::net::utils::SingleAccept; use crate::util::io::BackTrackingReader; use crate::Error; diff --git a/backend/src/os_install/fstab.template b/backend/src/os_install/fstab.template new file mode 100644 index 000000000..c299a0aae --- /dev/null +++ b/backend/src/os_install/fstab.template @@ -0,0 +1,3 @@ +{boot} /boot vfat umask=0077 0 2 +{efi} /boot/efi vfat umask=0077 0 1 +{root} / ext4 defaults 0 1 \ No newline at end of file diff --git a/backend/src/os_install/gpt.rs b/backend/src/os_install/gpt.rs new file mode 100644 index 000000000..414b05ac6 --- /dev/null +++ b/backend/src/os_install/gpt.rs @@ -0,0 +1,122 @@ +use color_eyre::eyre::eyre; +use gpt::disk::LogicalBlockSize; +use gpt::GptConfig; + +use crate::disk::util::DiskInfo; +use crate::disk::OsPartitionInfo; +use crate::os_install::partition_for; +use crate::Error; + +pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result { + { + let disk = disk.clone(); + tokio::task::spawn_blocking(move || { + let mut device = Box::new( + std::fs::File::options() + .read(true) + .write(true) + .open(&disk.logicalname)?, + ); + let (mut gpt, guid_part) = if overwrite { + let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( + u32::try_from((disk.capacity / 512) - 1).unwrap_or(0xFF_FF_FF_FF), + ); + mbr.overwrite_lba0(&mut device)?; + ( + GptConfig::new() + .writable(true) + .initialized(false) + .logical_block_size(LogicalBlockSize::Lb512) + .create_from_device(device, None)?, + None, + ) + } else { + let gpt = GptConfig::new() + .writable(true) + .initialized(true) + .logical_block_size(LogicalBlockSize::Lb512) + .open_from_device(device)?; + let mut guid_part = None; + for (idx, part_info) in disk + .partitions + .iter() + .enumerate() + .map(|(idx, x)| (idx + 1, x)) + { + if let Some(entry) = gpt.partitions().get(&(idx as u32)) { + if entry.first_lba >= 33556480 { + if idx < 3 { + guid_part = Some(entry.clone()) + } + break; + } + if part_info.guid.is_some() { + return Err(Error::new( + eyre!("Not enough space before embassy data"), + crate::ErrorKind::InvalidRequest, + )); + } + } + } + (gpt, guid_part) + }; + + gpt.update_partitions(Default::default())?; + + gpt.add_partition("efi", 100 * 1024 * 1024, gpt::partition_types::EFI, 0, None)?; + gpt.add_partition( + "boot", + 1024 * 1024 * 1024, + gpt::partition_types::LINUX_FS, + 0, + None, + )?; + gpt.add_partition( + "root", + 15 * 1024 * 1024 * 1024, + match *crate::ARCH { + "x86_64" => gpt::partition_types::LINUX_ROOT_X64, + "aarch64" => gpt::partition_types::LINUX_ROOT_ARM_64, + _ => gpt::partition_types::LINUX_FS, + }, + 0, + None, + )?; + + if overwrite { + gpt.add_partition( + "data", + gpt.find_free_sectors() + .iter() + .map(|(_, size)| *size * u64::from(*gpt.logical_block_size())) + .max() + .ok_or_else(|| { + Error::new( + eyre!("No free space left on device"), + crate::ErrorKind::BlockDevice, + ) + })?, + gpt::partition_types::LINUX_LVM, + 0, + None, + )?; + } else if let Some(guid_part) = guid_part { + let mut parts = gpt.partitions().clone(); + parts.insert(gpt.find_next_partition_id(), guid_part); + gpt.update_partitions(parts)?; + } + + gpt.write()?; + + Ok(()) + }) + .await + .unwrap()?; + } + + Ok(OsPartitionInfo { + efi: Some(partition_for(&disk.logicalname, 1)), + boot: partition_for(&disk.logicalname, 2), + root: partition_for(&disk.logicalname, 3), + }) +} diff --git a/backend/src/os_install/mbr.rs b/backend/src/os_install/mbr.rs new file mode 100644 index 000000000..07f90c13d --- /dev/null +++ b/backend/src/os_install/mbr.rs @@ -0,0 +1,91 @@ +use color_eyre::eyre::eyre; +use mbrman::{MBRPartitionEntry, CHS, MBR}; + +use crate::disk::util::DiskInfo; +use crate::disk::OsPartitionInfo; +use crate::os_install::partition_for; +use crate::Error; + +pub async fn partition(disk: &DiskInfo, overwrite: bool) -> Result { + { + let sectors = (disk.capacity / 512) as u32; + let disk = disk.clone(); + tokio::task::spawn_blocking(move || { + let mut file = std::fs::File::options() + .read(true) + .write(true) + .open(&disk.logicalname)?; + let (mut mbr, guid_part) = if overwrite { + (MBR::new_from(&mut file, 512, rand::random())?, None) + } else { + let mut mbr = MBR::read_from(&mut file, 512)?; + let mut guid_part = None; + for (idx, part_info) in disk + .partitions + .iter() + .enumerate() + .map(|(idx, x)| (idx + 1, x)) + { + if let Some(entry) = mbr.get_mut(idx) { + if entry.starting_lba >= 33556480 { + if idx < 3 { + guid_part = + Some(std::mem::replace(entry, MBRPartitionEntry::empty())) + } + break; + } + if part_info.guid.is_some() { + return Err(Error::new( + eyre!("Not enough space before embassy data"), + crate::ErrorKind::InvalidRequest, + )); + } + *entry = MBRPartitionEntry::empty(); + } + } + (mbr, guid_part) + }; + + mbr[1] = MBRPartitionEntry { + boot: 0x80, + first_chs: CHS::empty(), + sys: 0x0b, + last_chs: CHS::empty(), + starting_lba: 2048, + sectors: 2099200 - 2048, + }; + mbr[2] = MBRPartitionEntry { + boot: 0, + first_chs: CHS::empty(), + sys: 0x83, + last_chs: CHS::empty(), + starting_lba: 2099200, + sectors: 33556480 - 2099200, + }; + + if overwrite { + mbr[3] = MBRPartitionEntry { + boot: 0, + first_chs: CHS::empty(), + sys: 0x8e, + last_chs: CHS::empty(), + starting_lba: 33556480, + sectors: sectors - 33556480, + } + } else if let Some(guid_part) = guid_part { + mbr[3] = guid_part; + } + mbr.write_into(&mut file)?; + + Ok(()) + }) + .await + .unwrap()?; + } + + Ok(OsPartitionInfo { + efi: None, + boot: partition_for(&disk.logicalname, 1), + root: partition_for(&disk.logicalname, 2), + }) +} diff --git a/backend/src/os_install.rs b/backend/src/os_install/mod.rs similarity index 62% rename from backend/src/os_install.rs rename to backend/src/os_install/mod.rs index 92acfbd45..19321e58b 100644 --- a/backend/src/os_install.rs +++ b/backend/src/os_install/mod.rs @@ -1,7 +1,6 @@ use std::path::{Path, PathBuf}; use color_eyre::eyre::eyre; -use mbrman::{MBRPartitionEntry, CHS, MBR}; use models::Error; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; @@ -10,13 +9,18 @@ use tokio::process::Command; use crate::context::InstallContext; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; +use crate::disk::mount::filesystem::efivarfs::EfiVarFs; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::{MountGuard, TmpMountGuard}; -use crate::disk::util::DiskInfo; +use crate::disk::util::{DiskInfo, PartitionTable}; use crate::disk::OsPartitionInfo; -use crate::net::net_utils::{find_eth_iface, find_wifi_iface}; +use crate::net::utils::{find_eth_iface, find_wifi_iface}; use crate::util::serde::IoFormat; use crate::util::{display_none, Invoke}; +use crate::ARCH; + +mod gpt; +mod mbr; #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] @@ -87,12 +91,30 @@ pub fn partition_for(disk: impl AsRef, idx: usize) -> PathBuf { } } +async fn partition(disk: &mut DiskInfo, overwrite: bool) -> Result { + let partition_type = match (overwrite, disk.partition_table) { + (true, _) | (_, None) => { + if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() { + PartitionTable::Gpt + } else { + PartitionTable::Mbr + } + } + (_, Some(t)) => t, + }; + disk.partition_table = Some(partition_type); + match partition_type { + PartitionTable::Gpt => gpt::partition(disk, overwrite).await, + PartitionTable::Mbr => mbr::partition(disk, overwrite).await, + } +} + #[command(display(display_none))] pub async fn execute( #[arg] logicalname: PathBuf, #[arg(short = 'o')] mut overwrite: bool, ) -> Result<(), Error> { - let disk = crate::disk::util::list(&Default::default()) + let mut disk = crate::disk::util::list(&Default::default()) .await? .into_iter() .find(|d| &d.logicalname == &logicalname) @@ -106,110 +128,60 @@ pub async fn execute( let wifi_iface = find_wifi_iface().await?; overwrite |= disk.guid.is_none() && disk.partitions.iter().all(|p| p.guid.is_none()); - let sectors = (disk.capacity / 512) as u32; - tokio::task::spawn_blocking(move || { - let mut file = std::fs::File::options() - .read(true) - .write(true) - .open(&logicalname)?; - let (mut mbr, guid_part) = if overwrite { - (MBR::new_from(&mut file, 512, rand::random())?, None) - } else { - let mut mbr = MBR::read_from(&mut file, 512)?; - let mut guid_part = None; - for (idx, part_info) in disk - .partitions - .iter() - .enumerate() - .map(|(idx, x)| (idx + 1, x)) - { - if let Some(entry) = mbr.get_mut(idx) { - if entry.starting_lba >= 33556480 { - if idx < 3 { - guid_part = Some(std::mem::replace(entry, MBRPartitionEntry::empty())) - } - break; - } - if part_info.guid.is_some() { - return Err(Error::new( - eyre!("Not enough space before embassy data"), - crate::ErrorKind::InvalidRequest, - )); - } - *entry = MBRPartitionEntry::empty(); - } - } - (mbr, guid_part) - }; - mbr[1] = MBRPartitionEntry { - boot: 0x80, - first_chs: CHS::empty(), - sys: 0x0b, - last_chs: CHS::empty(), - starting_lba: 2048, - sectors: 2099200 - 2048, - }; - mbr[2] = MBRPartitionEntry { - boot: 0, - first_chs: CHS::empty(), - sys: 0x83, - last_chs: CHS::empty(), - starting_lba: 2099200, - sectors: 33556480 - 2099200, - }; + let part_info = partition(&mut disk, overwrite).await?; - if overwrite { - mbr[3] = MBRPartitionEntry { - boot: 0, - first_chs: CHS::empty(), - sys: 0x8e, - last_chs: CHS::empty(), - starting_lba: 33556480, - sectors: sectors - 33556480, - } - } else if let Some(guid_part) = guid_part { - mbr[3] = guid_part; - } - mbr.write_into(&mut file)?; - - Ok(()) - }) - .await - .unwrap()?; - - let boot_part = partition_for(&disk.logicalname, 1); - let root_part = partition_for(&disk.logicalname, 2); + if let Some(efi) = &part_info.efi { + Command::new("mkfs.vfat") + .arg(efi) + .invoke(crate::ErrorKind::DiskManagement) + .await?; + Command::new("fatlabel") + .arg(efi) + .arg("efi") + .invoke(crate::ErrorKind::DiskManagement) + .await?; + } Command::new("mkfs.vfat") - .arg(&boot_part) + .arg(&part_info.boot) .invoke(crate::ErrorKind::DiskManagement) .await?; Command::new("fatlabel") - .arg(&boot_part) + .arg(&part_info.boot) .arg("boot") .invoke(crate::ErrorKind::DiskManagement) .await?; Command::new("mkfs.ext4") - .arg(&root_part) + .arg(&part_info.root) .invoke(crate::ErrorKind::DiskManagement) .await?; Command::new("e2label") - .arg(&root_part) + .arg(&part_info.root) .arg("rootfs") .invoke(crate::ErrorKind::DiskManagement) .await?; - let rootfs = TmpMountGuard::mount(&BlockDev::new(&root_part), ReadWrite).await?; + let rootfs = TmpMountGuard::mount(&BlockDev::new(&part_info.root), ReadWrite).await?; tokio::fs::create_dir(rootfs.as_ref().join("config")).await?; tokio::fs::create_dir(rootfs.as_ref().join("next")).await?; let current = rootfs.as_ref().join("current"); tokio::fs::create_dir(¤t).await?; tokio::fs::create_dir(current.join("boot")).await?; - let boot = - MountGuard::mount(&BlockDev::new(&boot_part), current.join("boot"), ReadWrite).await?; + let boot = MountGuard::mount( + &BlockDev::new(&part_info.boot), + current.join("boot"), + ReadWrite, + ) + .await?; + + let efi = if let Some(efi) = &part_info.efi { + Some(MountGuard::mount(&BlockDev::new(efi), current.join("boot/efi"), ReadWrite).await?) + } else { + None + }; Command::new("unsquashfs") .arg("-n") @@ -223,10 +195,7 @@ pub async fn execute( tokio::fs::write( rootfs.as_ref().join("config/config.yaml"), IoFormat::Yaml.to_vec(&PostInstallConfig { - os_partitions: OsPartitionInfo { - boot: boot_part.clone(), - root: root_part.clone(), - }, + os_partitions: part_info.clone(), ethernet_interface: eth_iface, wifi_interface: wifi_iface, })?, @@ -237,8 +206,13 @@ pub async fn execute( current.join("etc/fstab"), format!( include_str!("fstab.template"), - boot = boot_part.display(), - root = root_part.display() + boot = part_info.boot.display(), + efi = part_info + .efi + .as_ref() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| "# N/A".to_owned()), + root = part_info.root.display(), ), ) .await?; @@ -257,26 +231,52 @@ pub async fn execute( .await?; let dev = MountGuard::mount(&Bind::new("/dev"), current.join("dev"), ReadWrite).await?; - let sys = MountGuard::mount(&Bind::new("/sys"), current.join("sys"), ReadWrite).await?; let proc = MountGuard::mount(&Bind::new("/proc"), current.join("proc"), ReadWrite).await?; + let sys = MountGuard::mount(&Bind::new("/sys"), current.join("sys"), ReadWrite).await?; + let efivarfs = if let Some(efi) = &part_info.efi { + Some( + MountGuard::mount( + &EfiVarFs, + current.join("sys/firmware/efi/efivars"), + ReadWrite, + ) + .await?, + ) + } else { + None + }; Command::new("chroot") .arg(¤t) .arg("update-grub") .invoke(crate::ErrorKind::Grub) .await?; - Command::new("chroot") - .arg(¤t) - .arg("grub-install") - .arg("--target=i386-pc") + let mut install = Command::new("chroot"); + install.arg(¤t).arg("grub-install"); + if part_info.efi.is_none() { + install.arg("--target=i386-pc"); + } else { + match *ARCH { + "x86_64" => install.arg("--target=x86_64-efi"), + "aarch64" => install.arg("--target=arm64-efi"), + _ => &mut install, + }; + } + install .arg(&disk.logicalname) .invoke(crate::ErrorKind::Grub) .await?; - dev.unmount().await?; - sys.unmount().await?; - proc.unmount().await?; - boot.unmount().await?; + dev.unmount(false).await?; + if let Some(efivarfs) = efivarfs { + efivarfs.unmount(false).await?; + } + sys.unmount(false).await?; + proc.unmount(false).await?; + if let Some(efi) = efi { + efi.unmount(false).await?; + } + boot.unmount(false).await?; rootfs.unmount().await?; Ok(()) diff --git a/backend/src/update/mod.rs b/backend/src/update/mod.rs index dc7146a25..c6b69708a 100644 --- a/backend/src/update/mod.rs +++ b/backend/src/update/mod.rs @@ -325,10 +325,10 @@ async fn sync_boot() -> Result<(), Error> { .arg("update-grub") .invoke(ErrorKind::MigrationFailed) .await?; - boot_mnt.unmount().await?; - proc_mnt.unmount().await?; - sys_mnt.unmount().await?; - dev_mnt.unmount().await?; + boot_mnt.unmount(false).await?; + proc_mnt.unmount(false).await?; + sys_mnt.unmount(false).await?; + dev_mnt.unmount(false).await?; } Ok(()) } diff --git a/build/raspberry-pi/make-image.sh b/build/raspberry-pi/make-image.sh index 384cb7e65..cd7aabfe6 100755 --- a/build/raspberry-pi/make-image.sh +++ b/build/raspberry-pi/make-image.sh @@ -10,7 +10,7 @@ function partition_for () { fi } -TARGET_NAME=embassyos-raspi.img +TARGET_NAME=embassyos-raspi-uninit.img TARGET_SIZE=2400000000 cp raspios.img $TARGET_NAME diff --git a/system-images/compat/Cargo.lock b/system-images/compat/Cargo.lock index 7b6fa8c28..0bee6dca6 100644 --- a/system-images/compat/Cargo.lock +++ b/system-images/compat/Cargo.lock @@ -426,6 +426,12 @@ dependencies = [ "serde", ] +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + [[package]] name = "bumpalo" version = "3.12.0" @@ -706,6 +712,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + [[package]] name = "crc" version = "3.0.0" @@ -1185,6 +1200,7 @@ dependencies = [ "fd-lock-rs", "futures", "git-version", + "gpt", "helpers", "hex", "hmac 0.12.1", @@ -1254,7 +1270,7 @@ dependencies = [ "trust-dns-server", "typed-builder", "url", - "uuid", + "uuid 1.2.2", "zeroize", ] @@ -1666,6 +1682,18 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "gpt" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd7365d734a70ac5dd7be791b0c96083852188df015b8c665bb2dadb108a743" +dependencies = [ + "bitflags", + "crc 1.8.1", + "log", + "uuid 0.8.2", +] + [[package]] name = "group" version = "0.12.1" @@ -4076,7 +4104,7 @@ dependencies = [ "byteorder", "bytes", "chrono", - "crc", + "crc 3.0.0", "crossbeam-queue", "dirs", "dotenvy", @@ -4897,6 +4925,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.8", +] + [[package]] name = "uuid" version = "1.2.2"