From e3786592b2346bfdf67d7eeda0cfec29e0808557 Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Thu, 24 Aug 2023 19:20:48 -0600 Subject: [PATCH] Feature/remove bollard (#2396) * wip * remove bollard, add podman feature * fix error message parsing * fix subcommand * fix typo * use com.docker.network.bridge.name for podman * fix parse error * handle podman network interface nuance * add libyajl2 * use podman repos * manually add criu * do not manually require criu * remove docker images during cleanup stage * force removal * increase img size * Update startos-iso.yaml * don't remove docker --- .github/workflows/startos-iso.yaml | 8 +- backend/Cargo.lock | 1 - backend/Cargo.toml | 2 +- backend/build-prod.sh | 5 +- backend/src/context/rpc.rs | 7 - backend/src/init.rs | 84 +++++----- backend/src/install/cleanup.rs | 46 +----- backend/src/install/mod.rs | 5 +- backend/src/manager/manager_seed.rs | 55 ++----- backend/src/manager/mod.rs | 83 ++-------- backend/src/procedure/docker.rs | 78 +++------ backend/src/s9pk/specv2.md | 28 ++++ backend/src/shutdown.rs | 19 ++- backend/src/util/docker.rs | 239 ++++++++++++++++++++++++++++ backend/src/util/mod.rs | 1 + build/lib/depends | 4 +- build/lib/scripts/postinst | 1 + build/raspberrypi/make-image.sh | 6 +- system-images/compat/Cargo.lock | 1 - 19 files changed, 400 insertions(+), 273 deletions(-) create mode 100644 backend/src/s9pk/specv2.md create mode 100644 backend/src/util/docker.rs diff --git a/.github/workflows/startos-iso.yaml b/.github/workflows/startos-iso.yaml index 1ec009dfba..b495acf5b1 100644 --- a/.github/workflows/startos-iso.yaml +++ b/.github/workflows/startos-iso.yaml @@ -12,6 +12,9 @@ on: - dev - unstable - dev-unstable + - podman + - dev-podman + - dev-unstable-podman runner: type: choice description: Runner @@ -39,7 +42,7 @@ on: env: NODEJS_VERSION: "18.15.0" - ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}' + ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev-podman''))[github.event.inputs.environment == ''NONE''] }}' jobs: all: @@ -139,6 +142,7 @@ jobs: with: repository: Start9Labs/startos-image-recipes path: startos-image-recipes + ref: feature/podman - name: Install dependencies run: | @@ -166,7 +170,7 @@ jobs: - run: "mv embassy-os-deb/embassyos_0.3.x-1_*.deb startos-image-recipes/overlays/deb/" - - run: "rm -rf embassy-os-deb ${{ steps.npm-cache-dir.outputs.dir }} $HOME/.cargo" + - run: "sudo rm -rf embassy-os-deb ${{ steps.npm-cache-dir.outputs.dir }} $HOME/.cargo" - name: Run iso build working-directory: startos-image-recipes diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 38356b433c..ec983eaf45 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -4665,7 +4665,6 @@ dependencies = [ "base64 0.13.1", "base64ct", "basic-cookies", - "bollard", "bytes", "chrono", "ciborium", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 99e449ab8d..caf0a70948 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -33,6 +33,7 @@ avahi-alias = ["avahi"] cli = [] sdk = [] daemon = [] +podman = [] [dependencies] aes = { version = "0.7.5", features = ["ctr"] } @@ -50,7 +51,6 @@ base32 = "0.4.0" base64 = "0.13.0" base64ct = "1.5.1" basic-cookies = "0.1.4" -bollard = "0.13.0" bytes = "1" chrono = { version = "0.4.19", features = ["serde"] } clap = "3.2.8" diff --git a/backend/build-prod.sh b/backend/build-prod.sh index c2a68b2517..97ca33279a 100755 --- a/backend/build-prod.sh +++ b/backend/build-prod.sh @@ -27,6 +27,9 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -v "$H cd .. FLAGS="" +if [[ "$ENVIRONMENT" =~ (^|-)podman($|-) ]]; then + FLAGS="podman,$FLAGS" +fi if [[ "$ENVIRONMENT" =~ (^|-)unstable($|-) ]]; then FLAGS="unstable,$FLAGS" fi @@ -56,7 +59,7 @@ else fi for ARCH in x86_64 aarch64 do - rust-musl-builder sh -c "(cd libs && cargo build --release --features $FLAGS --locked --bin embassy_container_init)" + rust-musl-builder sh -c "(cd libs && cargo build --release --locked --bin embassy_container_init)" if test $? -ne 0; then fail=true fi diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 6b9dd364ff..119393fb00 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -4,9 +4,7 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::time::Duration; -use bollard::Docker; use helpers::to_tmp_path; use josekit::jwk::Jwk; use patch_db::json_ptr::JsonPointer; @@ -111,7 +109,6 @@ pub struct RpcContextSeed { pub db: PatchDb, pub secret_store: PgPool, pub account: RwLock, - pub docker: Docker, pub net_controller: Arc, pub managers: ManagerMap, pub metrics_cache: RwLock>, @@ -189,9 +186,6 @@ impl RpcContext { let account = AccountInfo::load(&secret_store).await?; let db = base.db(&account).await?; tracing::info!("Opened PatchDB"); - let mut docker = Docker::connect_with_unix_defaults()?; - docker.set_timeout(Duration::from_secs(600)); - tracing::info!("Connected to Docker"); let net_controller = Arc::new( NetController::init( base.tor_control @@ -225,7 +219,6 @@ impl RpcContext { db, secret_store, account: RwLock::new(account), - docker, net_controller, managers, metrics_cache, diff --git a/backend/src/init.rs b/backend/src/init.rs index 3cacc0726b..2dbcbb9779 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -20,6 +20,7 @@ use crate::install::PKG_ARCHIVE_DIR; use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::sound::BEP; use crate::system::time; +use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL}; use crate::util::Invoke; use crate::{Error, ARCH}; @@ -289,51 +290,56 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { if tokio::fs::metadata(&tmp_dir).await.is_err() { tokio::fs::create_dir_all(&tmp_dir).await?; } - let tmp_docker = cfg.datadir().join("package-data/tmp/docker"); + let tmp_docker = cfg + .datadir() + .join(format!("package-data/tmp/{CONTAINER_TOOL}")); let tmp_docker_exists = tokio::fs::metadata(&tmp_docker).await.is_ok(); if should_rebuild && tmp_docker_exists { tokio::fs::remove_dir_all(&tmp_docker).await?; } - Command::new("systemctl") - .arg("stop") - .arg("docker") - .invoke(crate::ErrorKind::Docker) - .await?; - crate::disk::mount::util::bind(&tmp_docker, "/var/lib/docker", false).await?; - Command::new("systemctl") - .arg("reset-failed") - .arg("docker") - .invoke(crate::ErrorKind::Docker) - .await?; - Command::new("systemctl") - .arg("start") - .arg("docker") - .invoke(crate::ErrorKind::Docker) - .await?; + if CONTAINER_TOOL == "docker" { + Command::new("systemctl") + .arg("stop") + .arg("docker") + .invoke(crate::ErrorKind::Docker) + .await?; + } + crate::disk::mount::util::bind(&tmp_docker, CONTAINER_DATADIR, false).await?; + + if CONTAINER_TOOL == "docker" { + Command::new("systemctl") + .arg("reset-failed") + .arg("docker") + .invoke(crate::ErrorKind::Docker) + .await?; + Command::new("systemctl") + .arg("start") + .arg("docker") + .invoke(crate::ErrorKind::Docker) + .await?; + } tracing::info!("Mounted Docker Data"); - if should_rebuild || !tmp_docker_exists { - tracing::info!("Creating Docker Network"); - bollard::Docker::connect_with_unix_defaults()? - .create_network(bollard::network::CreateNetworkOptions { - name: "start9", - driver: "bridge", - ipam: bollard::models::Ipam { - config: Some(vec![bollard::models::IpamConfig { - subnet: Some("172.18.0.1/24".into()), - ..Default::default() - }]), - ..Default::default() - }, - options: { - let mut m = HashMap::new(); - m.insert("com.docker.network.bridge.name", "br-start9"); - m - }, - ..Default::default() - }) + if CONTAINER_TOOL == "podman" { + Command::new("podman") + .arg("run") + .arg("-d") + .arg("--rm") + .arg("--network=start9") + .arg("--name=netdummy") + .arg("start9/x_system/utils:latest") + .arg("sleep") + .arg("infinity") + .invoke(crate::ErrorKind::Docker) .await?; - tracing::info!("Created Docker Network"); + } + + if should_rebuild || !tmp_docker_exists { + if CONTAINER_TOOL == "docker" { + tracing::info!("Creating Docker Network"); + create_bridge_network("start9", "172.18.0.1/24", "br-start9").await?; + tracing::info!("Created Docker Network"); + } tracing::info!("Loading System Docker Images"); crate::install::load_images("/usr/lib/embassy/system-images").await?; @@ -345,7 +351,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result { } tracing::info!("Enabling Docker QEMU Emulation"); - Command::new("docker") + Command::new(CONTAINER_TOOL) .arg("run") .arg("--privileged") .arg("--rm") diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index 8d4b4c9085..0cc4cbbefe 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; -use bollard::image::{ListImagesOptions, RemoveImageOptions}; use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier}; use sqlx::{Executor, Postgres}; use tracing::instrument; @@ -104,46 +102,12 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Res let mut errors = ErrorCollection::new(); ctx.managers.remove(&(id.clone(), version.clone())).await; // docker images start9/$APP_ID/*:$VERSION -q | xargs docker rmi - let images = ctx - .docker - .list_images(Some(ListImagesOptions { - all: false, - filters: { - let mut f = HashMap::new(); - f.insert( - "reference".to_owned(), - vec![format!("start9/{}/*:{}", id, version)], - ); - f - }, - digests: false, - })) - .await - .apply(|res| errors.handle(res)); + let images = crate::util::docker::images_for(id, version).await?; errors.extend( - futures::future::join_all( - images - .into_iter() - .flatten() - .flat_map(|image| image.repo_tags) - .filter(|tag| { - tag.starts_with(&format!("start9/{}/", id)) - && tag.ends_with(&format!(":{}", version)) - }) - .map(|tag| async { - let tag = tag; // move into future - ctx.docker - .remove_image( - &tag, - Some(RemoveImageOptions { - force: true, - noprune: false, - }), - None, - ) - .await - }), - ) + futures::future::join_all(images.into_iter().map(|sha| async { + let sha = sha; // move into future + crate::util::docker::remove_image(&sha).await + })) .await, ); let pkg_archive_dir = ctx diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index beeb83a82c..c29a1d1f36 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -46,6 +46,7 @@ use crate::notifications::NotificationLevel; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::reader::S9pkReader; use crate::status::{MainStatus, Status}; +use crate::util::docker::CONTAINER_TOOL; use crate::util::io::{copy_and_shutdown, response_to_reader}; use crate::util::serde::{display_serializable, Port}; use crate::util::{display_none, AsyncFileExt, Version}; @@ -1087,7 +1088,7 @@ pub async fn install_s9pk( tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version); progress .track_read_during(progress_model.clone(), &ctx.db, || async { - let mut load = Command::new("docker") + let mut load = Command::new(CONTAINER_TOOL) .arg("load") .stdin(Stdio::piped()) .stderr(Stdio::piped()) @@ -1467,7 +1468,7 @@ pub fn load_images<'a, P: AsRef + 'a + Send + Sync>( let path = entry.path(); let ext = path.extension().and_then(|ext| ext.to_str()); if ext == Some("tar") || ext == Some("s9pk") { - let mut load = Command::new("docker") + let mut load = Command::new(CONTAINER_TOOL) .arg("load") .stdin(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/backend/src/manager/manager_seed.rs b/backend/src/manager/manager_seed.rs index 69ba94f404..c0ea72b5f4 100644 --- a/backend/src/manager/manager_seed.rs +++ b/backend/src/manager/manager_seed.rs @@ -1,8 +1,9 @@ -use bollard::container::{StopContainerOptions, WaitContainerOptions}; +use models::ErrorKind; use tokio_stream::StreamExt; use crate::context::RpcContext; use crate::s9pk::manifest::Manifest; +use crate::util::docker::stop_container; use crate::Error; /// This is helper structure for a service, the seed of the data that is needed for the manager_container @@ -14,50 +15,20 @@ pub struct ManagerSeed { impl ManagerSeed { pub async fn stop_container(&self) -> Result<(), Error> { - match self - .ctx - .docker - .stop_container( - &self.container_name, - Some(StopContainerOptions { - t: self - .manifest - .containers - .as_ref() - .and_then(|c| c.main.sigterm_timeout) - .map(|d| d.as_secs()) - .unwrap_or(30) as i64, - }), - ) - .await + match stop_container( + &self.container_name, + self.manifest + .containers + .as_ref() + .and_then(|c| c.main.sigterm_timeout) + .map(|d| *d), + None, + ) + .await { - Err(bollard::errors::Error::DockerResponseServerError { - status_code: 404, // NOT FOUND - .. - }) - | Err(bollard::errors::Error::DockerResponseServerError { - status_code: 409, // CONFLICT - .. - }) - | Err(bollard::errors::Error::DockerResponseServerError { - status_code: 304, // NOT MODIFIED - .. - }) => (), // Already stopped + Err(e) if e.kind == ErrorKind::NotFound => (), // Already stopped a => a?, } - - // Wait for the container to stop - { - let mut waiting = self.ctx.docker.wait_container( - &self.container_name, - Some(WaitContainerOptions { - condition: "not-running", - }), - ); - while let Some(_) = waiting.next().await { - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - } Ok(()) } } diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 66bfafe1ef..875e3eeff6 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -44,6 +44,7 @@ use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning}; use crate::procedure::{NoOutput, ProcedureName}; use crate::s9pk::manifest::Manifest; use crate::status::MainStatus; +use crate::util::docker::{get_container_ip, kill_container}; use crate::util::NonDetachingJoinHandle; use crate::volume::Volume; use crate::Error; @@ -228,7 +229,7 @@ impl Manager { .send_replace(Default::default()) .join_handle() { - transition.abort(); + (&**transition).abort(); } } fn _transition_replace(&self, transition_state: TransitionState) { @@ -741,26 +742,10 @@ enum GetRunningIp { async fn get_long_running_ip(seed: &ManagerSeed, runtime: &mut LongRunning) -> GetRunningIp { loop { - match container_inspect(seed).await { - Ok(res) => { - match res - .network_settings - .and_then(|ns| ns.networks) - .and_then(|mut n| n.remove("start9")) - .and_then(|es| es.ip_address) - .filter(|ip| !ip.is_empty()) - .map(|ip| ip.parse()) - .transpose() - { - Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr), - Ok(None) => (), - Err(e) => return GetRunningIp::Error(e.into()), - } - } - Err(bollard::errors::Error::DockerResponseServerError { - status_code: 404, // NOT FOUND - .. - }) => (), + match get_container_ip(&seed.container_name).await { + Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr), + Ok(None) => (), + Err(e) if e.kind == ErrorKind::NotFound => (), Err(e) => return GetRunningIp::Error(e.into()), } if let Poll::Ready(res) = futures::poll!(&mut runtime.running_output) { @@ -777,16 +762,6 @@ async fn get_long_running_ip(seed: &ManagerSeed, runtime: &mut LongRunning) -> G } } -#[instrument(skip(seed))] -async fn container_inspect( - seed: &ManagerSeed, -) -> Result { - seed.ctx - .docker - .inspect_container(&seed.container_name, None) - .await -} - #[instrument(skip(seed))] async fn add_network_for_main( seed: &ManagerSeed, @@ -851,26 +826,10 @@ type RuntimeOfCommand = NonDetachingJoinHandle GetRunningIp { loop { - match container_inspect(seed).await { - Ok(res) => { - match res - .network_settings - .and_then(|ns| ns.networks) - .and_then(|mut n| n.remove("start9")) - .and_then(|es| es.ip_address) - .filter(|ip| !ip.is_empty()) - .map(|ip| ip.parse()) - .transpose() - { - Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr), - Ok(None) => (), - Err(e) => return GetRunningIp::Error(e.into()), - } - } - Err(bollard::errors::Error::DockerResponseServerError { - status_code: 404, // NOT FOUND - .. - }) => (), + match get_container_ip(&seed.container_name).await { + Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr), + Ok(None) => (), + Err(e) if e.kind == ErrorKind::NotFound => (), Err(e) => return GetRunningIp::Error(e.into()), } if let Poll::Ready(res) = futures::poll!(&mut runtime) { @@ -933,28 +892,10 @@ async fn send_signal(manager: &Manager, gid: Arc, signal: Signal) -> Result } } else { // send signal to container - manager - .seed - .ctx - .docker - .kill_container( - &manager.seed.container_name, - Some(bollard::container::KillContainerOptions { - signal: signal.to_string(), - }), - ) + kill_container(&manager.seed.container_name, Some(signal)) .await .or_else(|e| { - if matches!( - e, - bollard::errors::Error::DockerResponseServerError { - status_code: 409, // CONFLICT - .. - } | bollard::errors::Error::DockerResponseServerError { - status_code: 404, // NOT FOUND - .. - } - ) { + if e.kind == ErrorKind::NotFound { Ok(()) } else { Err(e) diff --git a/backend/src/procedure/docker.rs b/backend/src/procedure/docker.rs index 0f8a71dda2..8a43eea195 100644 --- a/backend/src/procedure/docker.rs +++ b/backend/src/procedure/docker.rs @@ -7,7 +7,6 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use async_stream::stream; -use bollard::container::RemoveContainerOptions; use color_eyre::eyre::eyre; use color_eyre::Report; use futures::future::{BoxFuture, Either as EitherFuture}; @@ -26,6 +25,7 @@ use tracing::instrument; use super::ProcedureName; use crate::context::RpcContext; use crate::s9pk::manifest::{PackageId, SYSTEM_PACKAGE_ID}; +use crate::util::docker::{remove_container, CONTAINER_TOOL}; use crate::util::serde::{Duration as SerdeDuration, IoFormat}; use crate::util::Version; use crate::volume::{VolumeId, Volumes}; @@ -228,8 +228,8 @@ impl DockerProcedure { timeout: Option, ) -> Result, Error> { let name = name.docker_name(); - let name: Option<&str> = name.as_deref(); - let mut cmd = tokio::process::Command::new("docker"); + let name: Option<&str> = name.as_ref().map(|x| &**x); + let mut cmd = tokio::process::Command::new(CONTAINER_TOOL); let container_name = Self::container_name(pkg_id, name); cmd.arg("run") .arg("--rm") @@ -240,25 +240,7 @@ impl DockerProcedure { .arg(format!("--hostname={}", &container_name)) .arg("--no-healthcheck") .kill_on_drop(true); - match ctx - .docker - .remove_container( - &container_name, - Some(RemoveContainerOptions { - v: false, - force: true, - link: false, - }), - ) - .await - { - Ok(()) - | Err(bollard::errors::Error::DockerResponseServerError { - status_code: 404, // NOT FOUND - .. - }) => Ok(()), - Err(e) => Err(e), - }?; + remove_container(&container_name, true).await?; cmd.args(self.docker_args(ctx, pkg_id, pkg_version, volumes).await?); let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) { cmd.stdin(std::process::Stdio::piped()); @@ -407,7 +389,9 @@ impl DockerProcedure { input: Option, timeout: Option, ) -> Result, Error> { - let mut cmd = tokio::process::Command::new("docker"); + let name = name.docker_name(); + let name: Option<&str> = name.as_deref(); + let mut cmd = tokio::process::Command::new(CONTAINER_TOOL); cmd.arg("exec"); @@ -556,9 +540,9 @@ impl DockerProcedure { pkg_version: &Version, volumes: &Volumes, input: Option, - _timeout: Option, + timeout: Option, ) -> Result, Error> { - let mut cmd = tokio::process::Command::new("docker"); + let mut cmd = tokio::process::Command::new(CONTAINER_TOOL); cmd.arg("run").arg("--rm").arg("--network=none"); cmd.args( self.docker_args(ctx, pkg_id, pkg_version, &volumes.to_readonly()) @@ -639,7 +623,18 @@ impl DockerProcedure { } })); - let exit_status = handle.wait().await.with_kind(crate::ErrorKind::Docker)?; + let handle = if let Some(dur) = timeout { + async move { + tokio::time::timeout(dur, handle.wait()) + .await + .with_kind(crate::ErrorKind::Docker)? + .with_kind(crate::ErrorKind::Docker) + } + .boxed() + } else { + async { handle.wait().await.with_kind(crate::ErrorKind::Docker) }.boxed() + }; + let exit_status = handle.await?; Ok( if exit_status.success() || exit_status.code() == Some(143) { Ok(serde_json::from_value( @@ -820,10 +815,10 @@ impl LongRunning { const BIND_LOCATION: &str = "/usr/lib/embassy/container/"; tracing::trace!("setup_long_running_docker_cmd"); - LongRunning::cleanup_previous_container(ctx, container_name).await?; + remove_container(container_name, true).await?; let image_architecture = { - let mut cmd = tokio::process::Command::new("docker"); + let mut cmd = tokio::process::Command::new(CONTAINER_TOOL); cmd.arg("image") .arg("inspect") .arg("--format") @@ -838,7 +833,7 @@ impl LongRunning { arch.replace('\'', "").trim().to_string() }; - let mut cmd = tokio::process::Command::new("docker"); + let mut cmd = tokio::process::Command::new(CONTAINER_TOOL); cmd.arg("run") .arg("--network=start9") .arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP))) @@ -891,31 +886,6 @@ impl LongRunning { cmd.stdin(std::process::Stdio::piped()); Ok(cmd) } - - async fn cleanup_previous_container( - ctx: &RpcContext, - container_name: &str, - ) -> Result<(), Error> { - match ctx - .docker - .remove_container( - container_name, - Some(RemoveContainerOptions { - v: false, - force: true, - link: false, - }), - ) - .await - { - Ok(()) - | Err(bollard::errors::Error::DockerResponseServerError { - status_code: 404, // NOT FOUND - .. - }) => Ok(()), - Err(e) => Err(e)?, - } - } } async fn buf_reader_to_lines( reader: impl AsyncBufRead + Unpin, diff --git a/backend/src/s9pk/specv2.md b/backend/src/s9pk/specv2.md new file mode 100644 index 0000000000..9bf993463a --- /dev/null +++ b/backend/src/s9pk/specv2.md @@ -0,0 +1,28 @@ +## Header + +### Magic + +2B: `0x3b3b` + +### Version + +varint: `0x02` + +### Pubkey + +32B: ed25519 pubkey + +### TOC + +- number of sections (varint) +- FOREACH section + - sig (32B: ed25519 signature of BLAKE-3 of rest of section) + - name (varstring) + - TYPE (varint) + - TYPE=FILE (`0x01`) + - mime (varstring) + - pos (32B: u64 BE) + - len (32B: u64 BE) + - hash (32B: BLAKE-3 of file contents) + - TYPE=TOC (`0x02`) + - recursively defined diff --git a/backend/src/shutdown.rs b/backend/src/shutdown.rs index 8c5c515cb4..4941915fcc 100644 --- a/backend/src/shutdown.rs +++ b/backend/src/shutdown.rs @@ -7,6 +7,7 @@ use crate::context::RpcContext; use crate::disk::main::export; use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH}; use crate::sound::SHUTDOWN; +use crate::util::docker::CONTAINER_TOOL; use crate::util::{display_none, Invoke}; use crate::{Error, OS_ARCH}; @@ -43,14 +44,16 @@ impl Shutdown { tracing::error!("Error Stopping Journald: {}", e); tracing::debug!("{:?}", e); } - if let Err(e) = Command::new("systemctl") - .arg("stop") - .arg("docker") - .invoke(crate::ErrorKind::Docker) - .await - { - tracing::error!("Error Stopping Docker: {}", e); - tracing::debug!("{:?}", e); + if CONTAINER_TOOL == "docker" { + if let Err(e) = Command::new("systemctl") + .arg("stop") + .arg("docker") + .invoke(crate::ErrorKind::Docker) + .await + { + tracing::error!("Error Stopping Docker: {}", e); + tracing::debug!("{:?}", e); + } } if let Some(guid) = &self.disk_guid { if let Err(e) = export(guid, &self.datadir).await { diff --git a/backend/src/util/docker.rs b/backend/src/util/docker.rs new file mode 100644 index 0000000000..e6c4d3db6f --- /dev/null +++ b/backend/src/util/docker.rs @@ -0,0 +1,239 @@ +use std::net::Ipv4Addr; +use std::time::Duration; + +use models::{Error, ErrorKind, PackageId, ResultExt, Version}; +use nix::sys::signal::Signal; +use tokio::process::Command; + +use crate::util::Invoke; + +#[cfg(not(feature = "podman"))] +pub const CONTAINER_TOOL: &str = "docker"; +#[cfg(feature = "podman")] +pub const CONTAINER_TOOL: &str = "podman"; + +#[cfg(not(feature = "podman"))] +pub const CONTAINER_DATADIR: &str = "/var/lib/docker"; +#[cfg(feature = "podman")] +pub const CONTAINER_DATADIR: &str = "/var/lib/containers"; + +pub struct DockerImageSha(String); + +// docker images start9/${package}/*:${version} -q --no-trunc +pub async fn images_for( + package: &PackageId, + version: &Version, +) -> Result, Error> { + Ok(String::from_utf8( + Command::new(CONTAINER_TOOL) + .arg("images") + .arg(format!("start9/{package}/*:{version}")) + .arg("--no-trunc") + .arg("-q") + .invoke(ErrorKind::Docker) + .await?, + )? + .lines() + .map(|l| DockerImageSha(l.trim().to_owned())) + .collect()) +} + +// docker rmi -f ${sha} +pub async fn remove_image(sha: &DockerImageSha) -> Result<(), Error> { + match Command::new(CONTAINER_TOOL) + .arg("rmi") + .arg("-f") + .arg(&sha.0) + .invoke(ErrorKind::Docker) + .await + .map(|_| ()) + { + Err(e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such image") => + { + Ok(()) + } + a => a, + }?; + Ok(()) +} + +// docker image prune -f +pub async fn prune_images() -> Result<(), Error> { + Command::new(CONTAINER_TOOL) + .arg("image") + .arg("prune") + .arg("-f") + .invoke(ErrorKind::Docker) + .await?; + Ok(()) +} + +// docker container inspect ${name} --format '{{.NetworkSettings.Networks.start9.IPAddress}}' +pub async fn get_container_ip(name: &str) -> Result, Error> { + match Command::new(CONTAINER_TOOL) + .arg("container") + .arg("inspect") + .arg(name) + .arg("--format") + .arg("{{.NetworkSettings.Networks.start9.IPAddress}}") + .invoke(ErrorKind::Docker) + .await + { + Err(e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such container") => + { + Ok(None) + } + Err(e) => Err(e), + Ok(a) => { + let out = std::str::from_utf8(&a)?.trim(); + if out.is_empty() { + Ok(None) + } else { + Ok(Some({ + out.parse() + .with_ctx(|_| (ErrorKind::ParseNetAddress, out.to_string()))? + })) + } + } + } +} + +// docker stop -t ${timeout} -s ${signal} ${name} +pub async fn stop_container( + name: &str, + timeout: Option, + signal: Option, +) -> Result<(), Error> { + let mut cmd = Command::new(CONTAINER_TOOL); + cmd.arg("stop"); + if let Some(dur) = timeout { + cmd.arg("-t").arg(dur.as_secs().to_string()); + } + if let Some(sig) = signal { + cmd.arg("-s").arg(sig.to_string()); + } + cmd.arg(name); + match cmd.invoke(ErrorKind::Docker).await { + Ok(_) => Ok(()), + Err(mut e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such container") => + { + e.kind = ErrorKind::NotFound; + Err(e) + } + Err(e) => Err(e), + } +} + +// docker kill -s ${signal} ${name} +pub async fn kill_container(name: &str, signal: Option) -> Result<(), Error> { + let mut cmd = Command::new(CONTAINER_TOOL); + cmd.arg("kill"); + if let Some(sig) = signal { + cmd.arg("-s").arg(sig.to_string()); + } + cmd.arg(name); + match cmd.invoke(ErrorKind::Docker).await { + Ok(_) => Ok(()), + Err(mut e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such container") => + { + e.kind = ErrorKind::NotFound; + Err(e) + } + Err(e) => Err(e), + } +} + +// docker pause ${name} +pub async fn pause_container(name: &str) -> Result<(), Error> { + let mut cmd = Command::new(CONTAINER_TOOL); + cmd.arg("pause"); + cmd.arg(name); + match cmd.invoke(ErrorKind::Docker).await { + Ok(_) => Ok(()), + Err(mut e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such container") => + { + e.kind = ErrorKind::NotFound; + Err(e) + } + Err(e) => Err(e), + } +} + +// docker unpause ${name} +pub async fn unpause_container(name: &str) -> Result<(), Error> { + let mut cmd = Command::new(CONTAINER_TOOL); + cmd.arg("unpause"); + cmd.arg(name); + match cmd.invoke(ErrorKind::Docker).await { + Ok(_) => Ok(()), + Err(mut e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such container") => + { + e.kind = ErrorKind::NotFound; + Err(e) + } + Err(e) => Err(e), + } +} + +// docker rm -f ${name} +pub async fn remove_container(name: &str, force: bool) -> Result<(), Error> { + let mut cmd = Command::new(CONTAINER_TOOL); + cmd.arg("rm"); + if force { + cmd.arg("-f"); + } + cmd.arg(name); + match cmd.invoke(ErrorKind::Docker).await { + Ok(_) => Ok(()), + Err(e) + if e.source + .to_string() + .to_ascii_lowercase() + .contains("no such container") => + { + Ok(()) + } + Err(e) => Err(e), + } +} + +// docker network create -d bridge --subnet ${subnet} --opt com.podman.network.bridge.name=${bridge_name} +pub async fn create_bridge_network( + name: &str, + subnet: &str, + bridge_name: &str, +) -> Result<(), Error> { + let mut cmd = Command::new(CONTAINER_TOOL); + cmd.arg("network").arg("create"); + cmd.arg("-d").arg("bridge"); + cmd.arg("--subnet").arg(subnet); + cmd.arg("--opt") + .arg(format!("com.docker.network.bridge.name={bridge_name}")); + cmd.arg(name); + cmd.invoke(ErrorKind::Docker).await?; + Ok(()) +} diff --git a/backend/src/util/mod.rs b/backend/src/util/mod.rs index 0c4d66c202..a9daf76cc7 100644 --- a/backend/src/util/mod.rs +++ b/backend/src/util/mod.rs @@ -24,6 +24,7 @@ use tracing::instrument; use crate::shutdown::Shutdown; use crate::{Error, ErrorKind, ResultExt as _}; pub mod config; +pub mod docker; pub mod http_reader; pub mod io; pub mod logger; diff --git a/build/lib/depends b/build/lib/depends index 23d19660e5..b64d81ba8b 100644 --- a/build/lib/depends +++ b/build/lib/depends @@ -7,8 +7,8 @@ btrfs-progs ca-certificates cifs-utils containerd.io -curl cryptsetup +curl docker-ce docker-ce-cli docker-compose-plugin @@ -23,6 +23,7 @@ iotop iw jq libavahi-client3 +libyajl2 lm-sensors lshw lvm2 @@ -34,6 +35,7 @@ network-manager nvme-cli nyx openssh-server +podman postgresql psmisc qemu-guest-agent diff --git a/build/lib/scripts/postinst b/build/lib/scripts/postinst index e0dd72a391..d811d726ab 100755 --- a/build/lib/scripts/postinst +++ b/build/lib/scripts/postinst @@ -82,6 +82,7 @@ cat > /etc/docker/daemon.json << EOF "storage-driver": "overlay2" } EOF +podman network create -d bridge --subnet 172.18.0.1/24 --opt com.docker.network.bridge.name=br-start9 start9 mkdir -p /etc/nginx/ssl # fix to suppress docker warning, fixed in 21.xx release of docker cli: https://github.com/docker/cli/pull/2934 diff --git a/build/raspberrypi/make-image.sh b/build/raspberrypi/make-image.sh index 3a0bf52916..2c58fc079a 100755 --- a/build/raspberrypi/make-image.sh +++ b/build/raspberrypi/make-image.sh @@ -15,6 +15,8 @@ ENVIRONMENT=$(cat ENVIRONMENT.txt) GIT_HASH=$(cat GIT_HASH.txt | head -c 7) DATE=$(date +%Y%m%d) +ROOT_PART_END=7217792 + VERSION_FULL="$VERSION-$GIT_HASH" if [ -n "$ENVIRONMENT" ]; then @@ -22,7 +24,7 @@ if [ -n "$ENVIRONMENT" ]; then fi TARGET_NAME=startos-${VERSION_FULL}-${DATE}_raspberrypi.img -TARGET_SIZE=$[(6817791+1)*512] +TARGET_SIZE=$[($ROOT_PART_END+1)*512] rm -f $TARGET_NAME truncate -s $TARGET_SIZE $TARGET_NAME @@ -43,7 +45,7 @@ truncate -s $TARGET_SIZE $TARGET_NAME echo p echo 2 echo 526336 - echo 6817791 + echo $ROOT_PART_END echo a echo 1 echo w diff --git a/system-images/compat/Cargo.lock b/system-images/compat/Cargo.lock index 2ab91bf78c..bfa86c9070 100644 --- a/system-images/compat/Cargo.lock +++ b/system-images/compat/Cargo.lock @@ -4202,7 +4202,6 @@ dependencies = [ "base64 0.13.1", "base64ct", "basic-cookies", - "bollard", "bytes", "chrono", "ciborium",