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
This commit is contained in:
Aiden McClelland
2023-08-24 19:20:48 -06:00
committed by GitHub
parent d6eaf8d3d9
commit e3786592b2
19 changed files with 400 additions and 273 deletions

View File

@@ -12,6 +12,9 @@ on:
- dev - dev
- unstable - unstable
- dev-unstable - dev-unstable
- podman
- dev-podman
- dev-unstable-podman
runner: runner:
type: choice type: choice
description: Runner description: Runner
@@ -39,7 +42,7 @@ on:
env: env:
NODEJS_VERSION: "18.15.0" 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: jobs:
all: all:
@@ -139,6 +142,7 @@ jobs:
with: with:
repository: Start9Labs/startos-image-recipes repository: Start9Labs/startos-image-recipes
path: startos-image-recipes path: startos-image-recipes
ref: feature/podman
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -166,7 +170,7 @@ jobs:
- run: "mv embassy-os-deb/embassyos_0.3.x-1_*.deb startos-image-recipes/overlays/deb/" - 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 - name: Run iso build
working-directory: startos-image-recipes working-directory: startos-image-recipes

1
backend/Cargo.lock generated
View File

@@ -4665,7 +4665,6 @@ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"base64ct", "base64ct",
"basic-cookies", "basic-cookies",
"bollard",
"bytes", "bytes",
"chrono", "chrono",
"ciborium", "ciborium",

View File

@@ -33,6 +33,7 @@ avahi-alias = ["avahi"]
cli = [] cli = []
sdk = [] sdk = []
daemon = [] daemon = []
podman = []
[dependencies] [dependencies]
aes = { version = "0.7.5", features = ["ctr"] } aes = { version = "0.7.5", features = ["ctr"] }
@@ -50,7 +51,6 @@ base32 = "0.4.0"
base64 = "0.13.0" base64 = "0.13.0"
base64ct = "1.5.1" base64ct = "1.5.1"
basic-cookies = "0.1.4" basic-cookies = "0.1.4"
bollard = "0.13.0"
bytes = "1" bytes = "1"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
clap = "3.2.8" clap = "3.2.8"

View File

@@ -27,6 +27,9 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -v "$H
cd .. cd ..
FLAGS="" FLAGS=""
if [[ "$ENVIRONMENT" =~ (^|-)podman($|-) ]]; then
FLAGS="podman,$FLAGS"
fi
if [[ "$ENVIRONMENT" =~ (^|-)unstable($|-) ]]; then if [[ "$ENVIRONMENT" =~ (^|-)unstable($|-) ]]; then
FLAGS="unstable,$FLAGS" FLAGS="unstable,$FLAGS"
fi fi
@@ -56,7 +59,7 @@ else
fi fi
for ARCH in x86_64 aarch64 for ARCH in x86_64 aarch64
do 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 if test $? -ne 0; then
fail=true fail=true
fi fi

View File

@@ -4,9 +4,7 @@ use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use bollard::Docker;
use helpers::to_tmp_path; use helpers::to_tmp_path;
use josekit::jwk::Jwk; use josekit::jwk::Jwk;
use patch_db::json_ptr::JsonPointer; use patch_db::json_ptr::JsonPointer;
@@ -111,7 +109,6 @@ pub struct RpcContextSeed {
pub db: PatchDb, pub db: PatchDb,
pub secret_store: PgPool, pub secret_store: PgPool,
pub account: RwLock<AccountInfo>, pub account: RwLock<AccountInfo>,
pub docker: Docker,
pub net_controller: Arc<NetController>, pub net_controller: Arc<NetController>,
pub managers: ManagerMap, pub managers: ManagerMap,
pub metrics_cache: RwLock<Option<crate::system::Metrics>>, pub metrics_cache: RwLock<Option<crate::system::Metrics>>,
@@ -189,9 +186,6 @@ impl RpcContext {
let account = AccountInfo::load(&secret_store).await?; let account = AccountInfo::load(&secret_store).await?;
let db = base.db(&account).await?; let db = base.db(&account).await?;
tracing::info!("Opened PatchDB"); 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( let net_controller = Arc::new(
NetController::init( NetController::init(
base.tor_control base.tor_control
@@ -225,7 +219,6 @@ impl RpcContext {
db, db,
secret_store, secret_store,
account: RwLock::new(account), account: RwLock::new(account),
docker,
net_controller, net_controller,
managers, managers,
metrics_cache, metrics_cache,

View File

@@ -20,6 +20,7 @@ use crate::install::PKG_ARCHIVE_DIR;
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
use crate::sound::BEP; use crate::sound::BEP;
use crate::system::time; use crate::system::time;
use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL};
use crate::util::Invoke; use crate::util::Invoke;
use crate::{Error, ARCH}; use crate::{Error, ARCH};
@@ -289,51 +290,56 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
if tokio::fs::metadata(&tmp_dir).await.is_err() { if tokio::fs::metadata(&tmp_dir).await.is_err() {
tokio::fs::create_dir_all(&tmp_dir).await?; 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(); let tmp_docker_exists = tokio::fs::metadata(&tmp_docker).await.is_ok();
if should_rebuild && tmp_docker_exists { if should_rebuild && tmp_docker_exists {
tokio::fs::remove_dir_all(&tmp_docker).await?; tokio::fs::remove_dir_all(&tmp_docker).await?;
} }
Command::new("systemctl") if CONTAINER_TOOL == "docker" {
.arg("stop") Command::new("systemctl")
.arg("docker") .arg("stop")
.invoke(crate::ErrorKind::Docker) .arg("docker")
.await?; .invoke(crate::ErrorKind::Docker)
crate::disk::mount::util::bind(&tmp_docker, "/var/lib/docker", false).await?; .await?;
Command::new("systemctl") }
.arg("reset-failed") crate::disk::mount::util::bind(&tmp_docker, CONTAINER_DATADIR, false).await?;
.arg("docker")
.invoke(crate::ErrorKind::Docker) if CONTAINER_TOOL == "docker" {
.await?; Command::new("systemctl")
Command::new("systemctl") .arg("reset-failed")
.arg("start") .arg("docker")
.arg("docker") .invoke(crate::ErrorKind::Docker)
.invoke(crate::ErrorKind::Docker) .await?;
.await?; Command::new("systemctl")
.arg("start")
.arg("docker")
.invoke(crate::ErrorKind::Docker)
.await?;
}
tracing::info!("Mounted Docker Data"); tracing::info!("Mounted Docker Data");
if should_rebuild || !tmp_docker_exists { if CONTAINER_TOOL == "podman" {
tracing::info!("Creating Docker Network"); Command::new("podman")
bollard::Docker::connect_with_unix_defaults()? .arg("run")
.create_network(bollard::network::CreateNetworkOptions { .arg("-d")
name: "start9", .arg("--rm")
driver: "bridge", .arg("--network=start9")
ipam: bollard::models::Ipam { .arg("--name=netdummy")
config: Some(vec![bollard::models::IpamConfig { .arg("start9/x_system/utils:latest")
subnet: Some("172.18.0.1/24".into()), .arg("sleep")
..Default::default() .arg("infinity")
}]), .invoke(crate::ErrorKind::Docker)
..Default::default()
},
options: {
let mut m = HashMap::new();
m.insert("com.docker.network.bridge.name", "br-start9");
m
},
..Default::default()
})
.await?; .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"); tracing::info!("Loading System Docker Images");
crate::install::load_images("/usr/lib/embassy/system-images").await?; crate::install::load_images("/usr/lib/embassy/system-images").await?;
@@ -345,7 +351,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
} }
tracing::info!("Enabling Docker QEMU Emulation"); tracing::info!("Enabling Docker QEMU Emulation");
Command::new("docker") Command::new(CONTAINER_TOOL)
.arg("run") .arg("run")
.arg("--privileged") .arg("--privileged")
.arg("--rm") .arg("--rm")

View File

@@ -1,8 +1,6 @@
use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use bollard::image::{ListImagesOptions, RemoveImageOptions};
use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier}; use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier};
use sqlx::{Executor, Postgres}; use sqlx::{Executor, Postgres};
use tracing::instrument; use tracing::instrument;
@@ -104,46 +102,12 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Res
let mut errors = ErrorCollection::new(); let mut errors = ErrorCollection::new();
ctx.managers.remove(&(id.clone(), version.clone())).await; ctx.managers.remove(&(id.clone(), version.clone())).await;
// docker images start9/$APP_ID/*:$VERSION -q | xargs docker rmi // docker images start9/$APP_ID/*:$VERSION -q | xargs docker rmi
let images = ctx let images = crate::util::docker::images_for(id, version).await?;
.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));
errors.extend( errors.extend(
futures::future::join_all( futures::future::join_all(images.into_iter().map(|sha| async {
images let sha = sha; // move into future
.into_iter() crate::util::docker::remove_image(&sha).await
.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
}),
)
.await, .await,
); );
let pkg_archive_dir = ctx let pkg_archive_dir = ctx

View File

@@ -46,6 +46,7 @@ use crate::notifications::NotificationLevel;
use crate::s9pk::manifest::{Manifest, PackageId}; use crate::s9pk::manifest::{Manifest, PackageId};
use crate::s9pk::reader::S9pkReader; use crate::s9pk::reader::S9pkReader;
use crate::status::{MainStatus, Status}; use crate::status::{MainStatus, Status};
use crate::util::docker::CONTAINER_TOOL;
use crate::util::io::{copy_and_shutdown, response_to_reader}; use crate::util::io::{copy_and_shutdown, response_to_reader};
use crate::util::serde::{display_serializable, Port}; use crate::util::serde::{display_serializable, Port};
use crate::util::{display_none, AsyncFileExt, Version}; use crate::util::{display_none, AsyncFileExt, Version};
@@ -1087,7 +1088,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version); tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version);
progress progress
.track_read_during(progress_model.clone(), &ctx.db, || async { .track_read_during(progress_model.clone(), &ctx.db, || async {
let mut load = Command::new("docker") let mut load = Command::new(CONTAINER_TOOL)
.arg("load") .arg("load")
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
@@ -1467,7 +1468,7 @@ pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
let path = entry.path(); let path = entry.path();
let ext = path.extension().and_then(|ext| ext.to_str()); let ext = path.extension().and_then(|ext| ext.to_str());
if ext == Some("tar") || ext == Some("s9pk") { if ext == Some("tar") || ext == Some("s9pk") {
let mut load = Command::new("docker") let mut load = Command::new(CONTAINER_TOOL)
.arg("load") .arg("load")
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())

View File

@@ -1,8 +1,9 @@
use bollard::container::{StopContainerOptions, WaitContainerOptions}; use models::ErrorKind;
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::s9pk::manifest::Manifest; use crate::s9pk::manifest::Manifest;
use crate::util::docker::stop_container;
use crate::Error; use crate::Error;
/// This is helper structure for a service, the seed of the data that is needed for the manager_container /// 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 { impl ManagerSeed {
pub async fn stop_container(&self) -> Result<(), Error> { pub async fn stop_container(&self) -> Result<(), Error> {
match self match stop_container(
.ctx &self.container_name,
.docker self.manifest
.stop_container( .containers
&self.container_name, .as_ref()
Some(StopContainerOptions { .and_then(|c| c.main.sigterm_timeout)
t: self .map(|d| *d),
.manifest None,
.containers )
.as_ref() .await
.and_then(|c| c.main.sigterm_timeout)
.map(|d| d.as_secs())
.unwrap_or(30) as i64,
}),
)
.await
{ {
Err(bollard::errors::Error::DockerResponseServerError { Err(e) if e.kind == ErrorKind::NotFound => (), // Already stopped
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
a => a?, 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(()) Ok(())
} }
} }

View File

@@ -44,6 +44,7 @@ use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning};
use crate::procedure::{NoOutput, ProcedureName}; use crate::procedure::{NoOutput, ProcedureName};
use crate::s9pk::manifest::Manifest; use crate::s9pk::manifest::Manifest;
use crate::status::MainStatus; use crate::status::MainStatus;
use crate::util::docker::{get_container_ip, kill_container};
use crate::util::NonDetachingJoinHandle; use crate::util::NonDetachingJoinHandle;
use crate::volume::Volume; use crate::volume::Volume;
use crate::Error; use crate::Error;
@@ -228,7 +229,7 @@ impl Manager {
.send_replace(Default::default()) .send_replace(Default::default())
.join_handle() .join_handle()
{ {
transition.abort(); (&**transition).abort();
} }
} }
fn _transition_replace(&self, transition_state: TransitionState) { 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 { async fn get_long_running_ip(seed: &ManagerSeed, runtime: &mut LongRunning) -> GetRunningIp {
loop { loop {
match container_inspect(seed).await { match get_container_ip(&seed.container_name).await {
Ok(res) => { Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr),
match res Ok(None) => (),
.network_settings Err(e) if e.kind == ErrorKind::NotFound => (),
.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
..
}) => (),
Err(e) => return GetRunningIp::Error(e.into()), Err(e) => return GetRunningIp::Error(e.into()),
} }
if let Poll::Ready(res) = futures::poll!(&mut runtime.running_output) { 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<bollard::models::ContainerInspectResponse, bollard::errors::Error> {
seed.ctx
.docker
.inspect_container(&seed.container_name, None)
.await
}
#[instrument(skip(seed))] #[instrument(skip(seed))]
async fn add_network_for_main( async fn add_network_for_main(
seed: &ManagerSeed, seed: &ManagerSeed,
@@ -851,26 +826,10 @@ type RuntimeOfCommand = NonDetachingJoinHandle<Result<Result<NoOutput, (i32, Str
#[instrument(skip(seed, runtime))] #[instrument(skip(seed, runtime))]
async fn get_running_ip(seed: &ManagerSeed, mut runtime: &mut RuntimeOfCommand) -> GetRunningIp { async fn get_running_ip(seed: &ManagerSeed, mut runtime: &mut RuntimeOfCommand) -> GetRunningIp {
loop { loop {
match container_inspect(seed).await { match get_container_ip(&seed.container_name).await {
Ok(res) => { Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr),
match res Ok(None) => (),
.network_settings Err(e) if e.kind == ErrorKind::NotFound => (),
.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
..
}) => (),
Err(e) => return GetRunningIp::Error(e.into()), Err(e) => return GetRunningIp::Error(e.into()),
} }
if let Poll::Ready(res) = futures::poll!(&mut runtime) { if let Poll::Ready(res) = futures::poll!(&mut runtime) {
@@ -933,28 +892,10 @@ async fn send_signal(manager: &Manager, gid: Arc<Gid>, signal: Signal) -> Result
} }
} else { } else {
// send signal to container // send signal to container
manager kill_container(&manager.seed.container_name, Some(signal))
.seed
.ctx
.docker
.kill_container(
&manager.seed.container_name,
Some(bollard::container::KillContainerOptions {
signal: signal.to_string(),
}),
)
.await .await
.or_else(|e| { .or_else(|e| {
if matches!( if e.kind == ErrorKind::NotFound {
e,
bollard::errors::Error::DockerResponseServerError {
status_code: 409, // CONFLICT
..
} | bollard::errors::Error::DockerResponseServerError {
status_code: 404, // NOT FOUND
..
}
) {
Ok(()) Ok(())
} else { } else {
Err(e) Err(e)

View File

@@ -7,7 +7,6 @@ use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use async_stream::stream; use async_stream::stream;
use bollard::container::RemoveContainerOptions;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::Report; use color_eyre::Report;
use futures::future::{BoxFuture, Either as EitherFuture}; use futures::future::{BoxFuture, Either as EitherFuture};
@@ -26,6 +25,7 @@ use tracing::instrument;
use super::ProcedureName; use super::ProcedureName;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::s9pk::manifest::{PackageId, SYSTEM_PACKAGE_ID}; 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::serde::{Duration as SerdeDuration, IoFormat};
use crate::util::Version; use crate::util::Version;
use crate::volume::{VolumeId, Volumes}; use crate::volume::{VolumeId, Volumes};
@@ -228,8 +228,8 @@ impl DockerProcedure {
timeout: Option<Duration>, timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> { ) -> Result<Result<O, (i32, String)>, Error> {
let name = name.docker_name(); let name = name.docker_name();
let name: Option<&str> = name.as_deref(); let name: Option<&str> = name.as_ref().map(|x| &**x);
let mut cmd = tokio::process::Command::new("docker"); let mut cmd = tokio::process::Command::new(CONTAINER_TOOL);
let container_name = Self::container_name(pkg_id, name); let container_name = Self::container_name(pkg_id, name);
cmd.arg("run") cmd.arg("run")
.arg("--rm") .arg("--rm")
@@ -240,25 +240,7 @@ impl DockerProcedure {
.arg(format!("--hostname={}", &container_name)) .arg(format!("--hostname={}", &container_name))
.arg("--no-healthcheck") .arg("--no-healthcheck")
.kill_on_drop(true); .kill_on_drop(true);
match ctx remove_container(&container_name, true).await?;
.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),
}?;
cmd.args(self.docker_args(ctx, pkg_id, pkg_version, volumes).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) { let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) {
cmd.stdin(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped());
@@ -407,7 +389,9 @@ impl DockerProcedure {
input: Option<I>, input: Option<I>,
timeout: Option<Duration>, timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> { ) -> Result<Result<O, (i32, String)>, 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"); cmd.arg("exec");
@@ -556,9 +540,9 @@ impl DockerProcedure {
pkg_version: &Version, pkg_version: &Version,
volumes: &Volumes, volumes: &Volumes,
input: Option<I>, input: Option<I>,
_timeout: Option<Duration>, timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> { ) -> Result<Result<O, (i32, String)>, 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.arg("run").arg("--rm").arg("--network=none");
cmd.args( cmd.args(
self.docker_args(ctx, pkg_id, pkg_version, &volumes.to_readonly()) 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( Ok(
if exit_status.success() || exit_status.code() == Some(143) { if exit_status.success() || exit_status.code() == Some(143) {
Ok(serde_json::from_value( Ok(serde_json::from_value(
@@ -820,10 +815,10 @@ impl LongRunning {
const BIND_LOCATION: &str = "/usr/lib/embassy/container/"; const BIND_LOCATION: &str = "/usr/lib/embassy/container/";
tracing::trace!("setup_long_running_docker_cmd"); 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 image_architecture = {
let mut cmd = tokio::process::Command::new("docker"); let mut cmd = tokio::process::Command::new(CONTAINER_TOOL);
cmd.arg("image") cmd.arg("image")
.arg("inspect") .arg("inspect")
.arg("--format") .arg("--format")
@@ -838,7 +833,7 @@ impl LongRunning {
arch.replace('\'', "").trim().to_string() 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") cmd.arg("run")
.arg("--network=start9") .arg("--network=start9")
.arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP))) .arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP)))
@@ -891,31 +886,6 @@ impl LongRunning {
cmd.stdin(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped());
Ok(cmd) 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( async fn buf_reader_to_lines(
reader: impl AsyncBufRead + Unpin, reader: impl AsyncBufRead + Unpin,

View File

@@ -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

View File

@@ -7,6 +7,7 @@ use crate::context::RpcContext;
use crate::disk::main::export; use crate::disk::main::export;
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH}; use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
use crate::sound::SHUTDOWN; use crate::sound::SHUTDOWN;
use crate::util::docker::CONTAINER_TOOL;
use crate::util::{display_none, Invoke}; use crate::util::{display_none, Invoke};
use crate::{Error, OS_ARCH}; use crate::{Error, OS_ARCH};
@@ -43,14 +44,16 @@ impl Shutdown {
tracing::error!("Error Stopping Journald: {}", e); tracing::error!("Error Stopping Journald: {}", e);
tracing::debug!("{:?}", e); tracing::debug!("{:?}", e);
} }
if let Err(e) = Command::new("systemctl") if CONTAINER_TOOL == "docker" {
.arg("stop") if let Err(e) = Command::new("systemctl")
.arg("docker") .arg("stop")
.invoke(crate::ErrorKind::Docker) .arg("docker")
.await .invoke(crate::ErrorKind::Docker)
{ .await
tracing::error!("Error Stopping Docker: {}", e); {
tracing::debug!("{:?}", e); tracing::error!("Error Stopping Docker: {}", e);
tracing::debug!("{:?}", e);
}
} }
if let Some(guid) = &self.disk_guid { if let Some(guid) = &self.disk_guid {
if let Err(e) = export(guid, &self.datadir).await { if let Err(e) = export(guid, &self.datadir).await {

239
backend/src/util/docker.rs Normal file
View File

@@ -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<Vec<DockerImageSha>, 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<Option<Ipv4Addr>, 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<Duration>,
signal: Option<Signal>,
) -> 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<Signal>) -> 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(())
}

View File

@@ -24,6 +24,7 @@ use tracing::instrument;
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::{Error, ErrorKind, ResultExt as _}; use crate::{Error, ErrorKind, ResultExt as _};
pub mod config; pub mod config;
pub mod docker;
pub mod http_reader; pub mod http_reader;
pub mod io; pub mod io;
pub mod logger; pub mod logger;

View File

@@ -7,8 +7,8 @@ btrfs-progs
ca-certificates ca-certificates
cifs-utils cifs-utils
containerd.io containerd.io
curl
cryptsetup cryptsetup
curl
docker-ce docker-ce
docker-ce-cli docker-ce-cli
docker-compose-plugin docker-compose-plugin
@@ -23,6 +23,7 @@ iotop
iw iw
jq jq
libavahi-client3 libavahi-client3
libyajl2
lm-sensors lm-sensors
lshw lshw
lvm2 lvm2
@@ -34,6 +35,7 @@ network-manager
nvme-cli nvme-cli
nyx nyx
openssh-server openssh-server
podman
postgresql postgresql
psmisc psmisc
qemu-guest-agent qemu-guest-agent

View File

@@ -82,6 +82,7 @@ cat > /etc/docker/daemon.json << EOF
"storage-driver": "overlay2" "storage-driver": "overlay2"
} }
EOF 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 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 # fix to suppress docker warning, fixed in 21.xx release of docker cli: https://github.com/docker/cli/pull/2934

View File

@@ -15,6 +15,8 @@ ENVIRONMENT=$(cat ENVIRONMENT.txt)
GIT_HASH=$(cat GIT_HASH.txt | head -c 7) GIT_HASH=$(cat GIT_HASH.txt | head -c 7)
DATE=$(date +%Y%m%d) DATE=$(date +%Y%m%d)
ROOT_PART_END=7217792
VERSION_FULL="$VERSION-$GIT_HASH" VERSION_FULL="$VERSION-$GIT_HASH"
if [ -n "$ENVIRONMENT" ]; then if [ -n "$ENVIRONMENT" ]; then
@@ -22,7 +24,7 @@ if [ -n "$ENVIRONMENT" ]; then
fi fi
TARGET_NAME=startos-${VERSION_FULL}-${DATE}_raspberrypi.img TARGET_NAME=startos-${VERSION_FULL}-${DATE}_raspberrypi.img
TARGET_SIZE=$[(6817791+1)*512] TARGET_SIZE=$[($ROOT_PART_END+1)*512]
rm -f $TARGET_NAME rm -f $TARGET_NAME
truncate -s $TARGET_SIZE $TARGET_NAME truncate -s $TARGET_SIZE $TARGET_NAME
@@ -43,7 +45,7 @@ truncate -s $TARGET_SIZE $TARGET_NAME
echo p echo p
echo 2 echo 2
echo 526336 echo 526336
echo 6817791 echo $ROOT_PART_END
echo a echo a
echo 1 echo 1
echo w echo w

View File

@@ -4202,7 +4202,6 @@ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"base64ct", "base64ct",
"basic-cookies", "basic-cookies",
"bollard",
"bytes", "bytes",
"chrono", "chrono",
"ciborium", "ciborium",