* [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates
@ 2025-06-11 14:48 Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox v2 01/11] add proxmox-oci crate Filip Schauer
` (11 more replies)
0 siblings, 12 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
Add basic support for OCI (Open Container Initiative) images [0] as
container templates.
An OCI image can be for example obtained from Docker Hub:
Either using Docker:
```
$ docker pull httpd
$ docker save httpd > httpd.tar
```
Or using Podman:
When using Podman, the format needs to be explicitly specified,
otherwise it defaults to docker-archive.
```
$ podman pull httpd
$ podman save --format=oci-archive httpd > httpd.tar
```
The tarball can be uploaded to a storage as a container template and
then used during container creation. It is automatically detected that
the container template is an OCI image. The resulting container still
uses the existing LXC framework.
# Dependencies:
To be able to build `proxmox-oci`, the `oci-spec` crate is required as a
dependency. A patch from Christoph [1] packages the `oci-spec` crate as
a deb package. Alternatively if the `oci-spec` crate is not yet
packaged, it can be downloaded from crates.io.
Here is a little script to download the `oci-spec` crate along with its
dependencies:
```sh
download_crate() {
CRATE_NAME=$1
CRATE_VERSION=$2
CRATE_SHA256=$3
wget https://crates.io/api/v1/crates/$CRATE_NAME/$CRATE_VERSION/download
COMPUTED_SHA256=$(sha256sum download | awk '{ print $1 }')
if [ "$COMPUTED_SHA256" != "$CRATE_SHA256" ]; then
echo "Checksum mismatch"; exit 1
fi
tar -xf download
rm download
mv $CRATE_NAME-$CRATE_VERSION /usr/share/cargo/registry/
echo "{\"package\":\"$CRATE_SHA256\",\"files\":{}}" > /usr/share/cargo/registry/$CRATE_NAME-$CRATE_VERSION/.cargo-checksum.json
}
download_crate strsim 0.11.1 7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f
download_crate ident_case 1.0.1 b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39
download_crate darling_macro 0.20.11 fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead
download_crate darling_core 0.20.11 0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e
download_crate darling 0.20.11 fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee
download_crate proc-macro-error-attr2 2.0.0 96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5
download_crate derive_builder_core 0.20.2 2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8
download_crate thiserror-impl 2.0.0 22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972
download_crate rustversion 1.0.20 eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2
download_crate heck 0.5.0 2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea
download_crate proc-macro-error2 2.0.1 11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802
download_crate derive_builder_macro 0.20.2 ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c
download_crate thiserror 2.0.0 15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668
download_crate strum_macros 0.27.1 c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8
download_crate strum 0.27.1 f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32
download_crate getset 0.1.5 f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe
download_crate derive_builder 0.20.2 507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947
download_crate oci-spec 0.8.1 57e9beda9d92fac7bf4904c34c83340ef1024159faee67179a04e0277523da33
```
Since librust-oci-spec-dev is in the proxmox-oci/debian/control file, a
dummy package needs to be installed, so dpkg-checkbuilddeps does not
complain.
dummy_librust_oci_spec.equivs:
```
Package: librust-oci-spec-dev
Version: 0.8.1
Provides: librust-oci-spec-0.8+default-dev (= 0.8.1-1)
```
```
$ equivs-build dummy_librust_oci_spec.equivs
$ dpkg -i ./librust-oci-spec-dev_0.8.1_all.deb
```
# Build & install order:
OCI image support:
1. proxmox
2. proxmox-perl-rs
3. pve-container
.tar container template support:
1. pve-storage
2. pve-manager
[0] https://github.com/opencontainers/image-spec/blob/main/spec.md
[1] https://lore.proxmox.com/pve-devel/20250606103719.533030-2-c.heiss@proxmox.com/
Changed since v1:
* Fix entrypoint command missing Cmd
* Set lxc.signal.halt according to StopSignal (Fixes container shutdown)
* setup: Ensure that both /etc/systemd/network and
/etc/systemd/system-preset exist before writing files into them.
* ui: storage upload: accept *.tar files as vztmpl
* proxmox-perl-rs: rebase on latest master (3d9806cb3c7f)
* proxmox-perl-rs: add new dependencies to debian/control
* proxmox-oci: refactor errors and use `thiserror` to avoid boilerplate
proxmox:
Filip Schauer (1):
add proxmox-oci crate
Cargo.toml | 1 +
proxmox-oci/Cargo.toml | 22 ++++
proxmox-oci/debian/changelog | 5 +
proxmox-oci/debian/control | 47 ++++++++
proxmox-oci/debian/debcargo.toml | 7 ++
proxmox-oci/src/lib.rs | 196 +++++++++++++++++++++++++++++++
proxmox-oci/src/oci_tar_image.rs | 167 ++++++++++++++++++++++++++
7 files changed, 445 insertions(+)
create mode 100644 proxmox-oci/Cargo.toml
create mode 100644 proxmox-oci/debian/changelog
create mode 100644 proxmox-oci/debian/control
create mode 100644 proxmox-oci/debian/debcargo.toml
create mode 100644 proxmox-oci/src/lib.rs
create mode 100644 proxmox-oci/src/oci_tar_image.rs
proxmox-perl-rs:
Filip Schauer (1):
add Perl mapping for OCI container image parser/extractor
pve-rs/Cargo.toml | 2 ++
pve-rs/Makefile | 1 +
pve-rs/debian/control | 2 ++
pve-rs/src/lib.rs | 1 +
pve-rs/src/oci.rs | 20 ++++++++++++++++++++
5 files changed, 26 insertions(+)
create mode 100644 pve-rs/src/oci.rs
pve-container:
Filip Schauer (7):
config: whitelist lxc.init.cwd
add support for OCI images as container templates
config: add entrypoint parameter
configure static IP in LXC config for custom entrypoint
setup: debian: create /etc/network path if missing
setup: recursively mkdir /etc/systemd/{network,system-preset}
manage DHCP for containers with custom entrypoint
src/PVE/API2/LXC.pm | 64 +++++++++++++++++++++++++--
src/PVE/LXC.pm | 88 ++++++++++++++++++++++++++++++++++---
src/PVE/LXC/Config.pm | 19 +++++++-
src/PVE/LXC/Setup/Base.pm | 3 +-
src/PVE/LXC/Setup/Debian.pm | 1 +
5 files changed, 165 insertions(+), 10 deletions(-)
pve-storage:
Filip Schauer (1):
allow .tar container templates
src/PVE/Storage.pm | 2 +-
src/PVE/Storage/Plugin.pm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
pve-manager:
Filip Schauer (1):
ui: storage upload: accept *.tar files as vztmpl
www/manager6/window/UploadToStorage.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Summary over all repositories:
20 files changed, 639 insertions(+), 13 deletions(-)
--
Generated by git-murpp 0.6.0
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH proxmox v2 01/11] add proxmox-oci crate
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox-perl-rs v2 02/11] add Perl mapping for OCI container image parser/extractor Filip Schauer
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
This crate can parse and extract an OCI image bundled as a tar archive.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
Cargo.toml | 1 +
proxmox-oci/Cargo.toml | 22 ++++
proxmox-oci/debian/changelog | 5 +
proxmox-oci/debian/control | 47 ++++++++
proxmox-oci/debian/debcargo.toml | 7 ++
proxmox-oci/src/lib.rs | 196 +++++++++++++++++++++++++++++++
proxmox-oci/src/oci_tar_image.rs | 167 ++++++++++++++++++++++++++
7 files changed, 445 insertions(+)
create mode 100644 proxmox-oci/Cargo.toml
create mode 100644 proxmox-oci/debian/changelog
create mode 100644 proxmox-oci/debian/control
create mode 100644 proxmox-oci/debian/debcargo.toml
create mode 100644 proxmox-oci/src/lib.rs
create mode 100644 proxmox-oci/src/oci_tar_image.rs
diff --git a/Cargo.toml b/Cargo.toml
index bf9e83d7..8365b18a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ members = [
"proxmox-metrics",
"proxmox-network-api",
"proxmox-notify",
+ "proxmox-oci",
"proxmox-openid",
"proxmox-product-config",
"proxmox-rest-server",
diff --git a/proxmox-oci/Cargo.toml b/proxmox-oci/Cargo.toml
new file mode 100644
index 00000000..4daff6ab
--- /dev/null
+++ b/proxmox-oci/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "proxmox-oci"
+description = "OCI image parsing and extraction"
+version = "0.1.0"
+
+authors.workspace = true
+edition.workspace = true
+exclude.workspace = true
+homepage.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+flate2.workspace = true
+oci-spec = "0.8.1"
+sha2 = "0.10"
+tar.workspace = true
+thiserror = "1"
+zstd.workspace = true
+
+proxmox-io.workspace = true
diff --git a/proxmox-oci/debian/changelog b/proxmox-oci/debian/changelog
new file mode 100644
index 00000000..754d06c1
--- /dev/null
+++ b/proxmox-oci/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-oci (0.1.0-1) bookworm; urgency=medium
+
+ * Initial release.
+
+ -- Proxmox Support Team <support@proxmox.com> Mon, 28 Apr 2025 12:34:56 +0200
diff --git a/proxmox-oci/debian/control b/proxmox-oci/debian/control
new file mode 100644
index 00000000..3974cf48
--- /dev/null
+++ b/proxmox-oci/debian/control
@@ -0,0 +1,47 @@
+Source: rust-proxmox-oci
+Section: rust
+Priority: optional
+Build-Depends: debhelper-compat (= 13),
+ dh-sequence-cargo
+Build-Depends-Arch: cargo:native <!nocheck>,
+ rustc:native (>= 1.82) <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-flate2-1+default-dev <!nocheck>,
+ librust-oci-spec-0.8+default-dev (>= 0.8.1-~~) <!nocheck>,
+ librust-proxmox-io-1+default-dev (>= 1.1.0-~~) <!nocheck>,
+ librust-sha2-0.10+default-dev <!nocheck>,
+ librust-tar-0.4+default-dev <!nocheck>,
+ librust-thiserror-1+default-dev <!nocheck>,
+ librust-zstd-0.12+bindgen-dev <!nocheck>,
+ librust-zstd-0.12+default-dev <!nocheck>
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Standards-Version: 4.7.0
+Vcs-Git: git://git.proxmox.com/git/proxmox.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
+Homepage: https://proxmox.com
+X-Cargo-Crate: proxmox-oci
+Rules-Requires-Root: no
+
+Package: librust-proxmox-oci-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-flate2-1+default-dev,
+ librust-oci-spec-0.8+default-dev (>= 0.8.1-~~),
+ librust-proxmox-io-1+default-dev (>= 1.1.0-~~),
+ librust-sha2-0.10+default-dev,
+ librust-tar-0.4+default-dev,
+ librust-thiserror-1+default-dev,
+ librust-zstd-0.12+bindgen-dev,
+ librust-zstd-0.12+default-dev
+Provides:
+ librust-proxmox-oci+default-dev (= ${binary:Version}),
+ librust-proxmox-oci-0-dev (= ${binary:Version}),
+ librust-proxmox-oci-0+default-dev (= ${binary:Version}),
+ librust-proxmox-oci-0.1-dev (= ${binary:Version}),
+ librust-proxmox-oci-0.1+default-dev (= ${binary:Version}),
+ librust-proxmox-oci-0.1.0-dev (= ${binary:Version}),
+ librust-proxmox-oci-0.1.0+default-dev (= ${binary:Version})
+Description: OCI image parsing and extraction - Rust source code
+ Source code for Debianized Rust crate "proxmox-oci"
diff --git a/proxmox-oci/debian/debcargo.toml b/proxmox-oci/debian/debcargo.toml
new file mode 100644
index 00000000..b7864cdb
--- /dev/null
+++ b/proxmox-oci/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
diff --git a/proxmox-oci/src/lib.rs b/proxmox-oci/src/lib.rs
new file mode 100644
index 00000000..cc5a1d46
--- /dev/null
+++ b/proxmox-oci/src/lib.rs
@@ -0,0 +1,196 @@
+use flate2::read::GzDecoder;
+use oci_spec::image::{Arch, Config, ImageConfiguration, ImageManifest, MediaType};
+use oci_spec::OciSpecError;
+use oci_tar_image::OciTarImage;
+use sha2::digest::generic_array::GenericArray;
+use sha2::{Digest, Sha256};
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::{Read, Seek};
+use std::path::PathBuf;
+use std::str::FromStr;
+use tar::Archive;
+use thiserror::Error;
+
+pub mod oci_tar_image;
+
+fn compute_digest<R: Read, H: Digest>(
+ mut reader: R,
+ mut hasher: H,
+) -> GenericArray<u8, H::OutputSize> {
+ let mut buf = proxmox_io::boxed::zeroed(4096);
+
+ loop {
+ let bytes_read = reader.read(&mut buf).unwrap();
+ if bytes_read == 0 {
+ break hasher.finalize();
+ }
+
+ hasher.update(&buf[..bytes_read]);
+ }
+}
+
+fn compute_sha256<R: Read>(reader: R) -> oci_spec::image::Sha256Digest {
+ let digest = compute_digest(reader, Sha256::new());
+ oci_spec::image::Sha256Digest::from_str(&format!("{:x}", digest)).unwrap()
+}
+
+/// Build a mapping from uncompressed layer digests (as found in the image config's `rootfs.diff_ids`)
+/// to their corresponding compressed-layer digests (i.e. the filenames under `blobs/<algorithm>/<digest>`)
+fn build_layer_map<R: Read + Seek>(
+ oci_tar_image: &mut OciTarImage<R>,
+ image_manifest: &ImageManifest,
+) -> HashMap<oci_spec::image::Digest, oci_spec::image::Descriptor> {
+ let mut layer_mapping = HashMap::new();
+
+ for layer in image_manifest.layers() {
+ let digest = match layer.media_type() {
+ MediaType::ImageLayer | MediaType::ImageLayerNonDistributable => {
+ Some(layer.digest().clone())
+ }
+ MediaType::ImageLayerGzip | MediaType::ImageLayerNonDistributableGzip => {
+ let compressed_blob = oci_tar_image.open_blob(layer.digest()).unwrap();
+ let decoder = GzDecoder::new(compressed_blob);
+ Some(compute_sha256(decoder).into())
+ }
+ MediaType::ImageLayerZstd | MediaType::ImageLayerNonDistributableZstd => {
+ let compressed_blob = oci_tar_image.open_blob(layer.digest()).unwrap();
+ let decoder = zstd::Decoder::new(compressed_blob).unwrap();
+ Some(compute_sha256(decoder).into())
+ }
+ _ => None,
+ };
+
+ if let Some(digest) = digest {
+ layer_mapping.insert(digest, layer.clone());
+ }
+ }
+
+ layer_mapping
+}
+
+#[derive(Debug, Error)]
+pub enum ProxmoxOciError {
+ #[error("Error while parsing OCI image: {0}")]
+ ParseError(#[from] ParseError),
+ #[error("Error while extracting OCI image: {0}")]
+ ExtractError(#[from] ExtractError),
+}
+
+pub fn parse_and_extract_image(
+ oci_tar_path: &str,
+ rootfs_path: &str,
+) -> Result<Option<Config>, ProxmoxOciError> {
+ let (mut oci_tar_image, image_manifest, image_config) = parse_image(oci_tar_path)?;
+
+ extract_image_rootfs(
+ &mut oci_tar_image,
+ &image_manifest,
+ &image_config,
+ rootfs_path.into(),
+ )?;
+
+ Ok(image_config.config().clone())
+}
+
+#[derive(Debug, Error)]
+pub enum ParseError {
+ #[error("Not an OCI image: {0}")]
+ NotAnOciImage(OciSpecError),
+ #[error("Wrong media type")]
+ WrongMediaType,
+ #[error("IO error: {0}")]
+ IoError(#[from] std::io::Error),
+ #[error("Unsupported CPU architecture")]
+ UnsupportedArchitecture,
+ #[error("Missing image config")]
+ MissingImageConfig,
+}
+
+impl From<OciSpecError> for ParseError {
+ fn from(oci_spec_err: OciSpecError) -> Self {
+ match oci_spec_err {
+ OciSpecError::Io(ioerr) => Self::IoError(ioerr),
+ ocierr => Self::NotAnOciImage(ocierr),
+ }
+ }
+}
+
+fn parse_image(
+ oci_tar_path: &str,
+) -> Result<(OciTarImage<File>, ImageManifest, ImageConfiguration), ParseError> {
+ let oci_tar_file = File::open(oci_tar_path)?;
+ let mut oci_tar_image = OciTarImage::new(oci_tar_file)?;
+
+ let image_manifest = oci_tar_image
+ .image_manifest(&Arch::Amd64)
+ .ok_or(ParseError::UnsupportedArchitecture)??;
+
+ let image_config_descriptor = image_manifest.config();
+
+ if image_config_descriptor.media_type() != &MediaType::ImageConfig {
+ return Err(ParseError::WrongMediaType);
+ }
+
+ let image_config_file = oci_tar_image
+ .open_blob(image_config_descriptor.digest())
+ .ok_or(ParseError::MissingImageConfig)?;
+ let image_config = ImageConfiguration::from_reader(image_config_file)?;
+
+ Ok((oci_tar_image, image_manifest, image_config))
+}
+
+#[derive(Debug, Error)]
+pub enum ExtractError {
+ #[error("Rootfs destination path not found")]
+ RootfsDestinationNotFound,
+ #[error("Unknown layer digest found in rootfs.diff_ids")]
+ UnknownLayerDigest,
+ #[error("Layer file mentioned in image manifest is missing")]
+ MissingLayerFile,
+ #[error("IO error: {0}")]
+ IoError(#[from] std::io::Error),
+ #[error("Layer has wrong media type")]
+ WrongMediaType,
+}
+
+fn extract_image_rootfs<R: Read + Seek>(
+ oci_tar_image: &mut OciTarImage<R>,
+ image_manifest: &ImageManifest,
+ image_config: &ImageConfiguration,
+ target_path: PathBuf,
+) -> Result<(), ExtractError> {
+ if !target_path.exists() {
+ return Err(ExtractError::RootfsDestinationNotFound);
+ }
+
+ let layer_map = build_layer_map(oci_tar_image, image_manifest);
+
+ for layer in image_config.rootfs().diff_ids() {
+ let layer_digest = oci_spec::image::Digest::from_str(layer)
+ .map_err(|_| ExtractError::UnknownLayerDigest)?;
+ let layer_descriptor = layer_map
+ .get(&layer_digest)
+ .ok_or(ExtractError::UnknownLayerDigest)?;
+ let layer_file = oci_tar_image
+ .open_blob(layer_descriptor.digest())
+ .ok_or(ExtractError::MissingLayerFile)?;
+
+ let tar_file: Box<dyn Read> = match layer_descriptor.media_type() {
+ MediaType::ImageLayer | MediaType::ImageLayerNonDistributable => Box::new(layer_file),
+ MediaType::ImageLayerGzip | MediaType::ImageLayerNonDistributableGzip => {
+ Box::new(GzDecoder::new(layer_file))
+ }
+ MediaType::ImageLayerZstd | MediaType::ImageLayerNonDistributableZstd => {
+ Box::new(zstd::Decoder::new(layer_file)?)
+ }
+ _ => return Err(ExtractError::WrongMediaType),
+ };
+
+ let mut archive = Archive::new(tar_file);
+ archive.set_preserve_ownerships(true);
+ archive.unpack(&target_path)?;
+ }
+
+ Ok(())
+}
diff --git a/proxmox-oci/src/oci_tar_image.rs b/proxmox-oci/src/oci_tar_image.rs
new file mode 100644
index 00000000..555558f5
--- /dev/null
+++ b/proxmox-oci/src/oci_tar_image.rs
@@ -0,0 +1,167 @@
+use oci_spec::image::{Arch, Digest, ImageIndex, ImageManifest, MediaType};
+use oci_spec::OciSpecError;
+use std::cmp::min;
+use std::collections::HashMap;
+use std::io::{Read, Seek, SeekFrom};
+use std::path::PathBuf;
+use tar::Archive;
+
+#[derive(Clone)]
+pub struct TarEntry {
+ offset: u64,
+ size: usize,
+}
+
+pub struct TarEntryReader<'a, R: Read + Seek> {
+ tar_entry: TarEntry,
+ reader: &'a mut R,
+ position: u64,
+}
+
+impl<'a, R: Read + Seek> TarEntryReader<'a, R> {
+ pub fn new(tar_entry: TarEntry, reader: &'a mut R) -> Self {
+ Self {
+ tar_entry,
+ reader,
+ position: 0,
+ }
+ }
+}
+
+impl<R: Read + Seek> Read for TarEntryReader<'_, R> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ let max_read = min(buf.len(), self.tar_entry.size - self.position as usize);
+ let limited_buf = &mut buf[..max_read];
+ self.reader
+ .seek(SeekFrom::Start(self.tar_entry.offset + self.position))?;
+ let bytes_read = self.reader.read(limited_buf)?;
+ self.position += bytes_read as u64;
+
+ Ok(bytes_read)
+ }
+}
+
+impl<R: Read + Seek> Seek for TarEntryReader<'_, R> {
+ fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
+ self.position = match pos {
+ SeekFrom::Start(position) => min(position, self.tar_entry.size as u64),
+ SeekFrom::End(offset) => {
+ if offset > self.tar_entry.size as i64 {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotSeekable,
+ "Tried to seek before the beginning of the file",
+ ));
+ }
+
+ (if offset <= 0 {
+ self.tar_entry.size
+ } else {
+ self.tar_entry.size - offset as usize
+ }) as u64
+ }
+ SeekFrom::Current(offset) => {
+ if (self.position as i64 + offset) < 0 {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotSeekable,
+ "Tried to seek before the beginning of the file",
+ ));
+ }
+
+ min(self.position + offset as u64, self.tar_entry.size as u64)
+ }
+ };
+
+ Ok(self.position)
+ }
+}
+
+struct TarArchive<R: Read + Seek> {
+ reader: R,
+ entries: HashMap<PathBuf, TarEntry>,
+}
+
+impl<R: Read + Seek> TarArchive<R> {
+ pub fn new(reader: R) -> std::io::Result<Self> {
+ let mut archive = Archive::new(reader);
+ let entries = archive.entries_with_seek()?;
+ let mut entries_index = HashMap::new();
+
+ for entry in entries {
+ let entry = entry?;
+ let offset = entry.raw_file_position();
+ let size = entry.size() as usize;
+ let path = entry.path()?.into_owned();
+ let tar_entry = TarEntry { offset, size };
+ entries_index.insert(path, tar_entry);
+ }
+
+ Ok(Self {
+ reader: archive.into_inner(),
+ entries: entries_index,
+ })
+ }
+
+ pub fn get_inner_file(&mut self, inner_file_path: PathBuf) -> Option<TarEntryReader<R>> {
+ match self.entries.get(&inner_file_path) {
+ Some(tar_entry) => Some(TarEntryReader::new(tar_entry.clone(), &mut self.reader)),
+ None => None,
+ }
+ }
+}
+
+pub struct OciTarImage<R: Read + Seek> {
+ archive: TarArchive<R>,
+ image_index: ImageIndex,
+}
+
+impl<R: Read + Seek> OciTarImage<R> {
+ pub fn new(reader: R) -> oci_spec::Result<Self> {
+ let mut archive = TarArchive::new(reader)?;
+ let index_file = archive
+ .get_inner_file("index.json".into())
+ .ok_or(OciSpecError::Other("Missing index.json file".into()))?;
+ let image_index = ImageIndex::from_reader(index_file)?;
+
+ Ok(Self {
+ archive,
+ image_index,
+ })
+ }
+
+ pub fn image_index(&self) -> &ImageIndex {
+ &self.image_index
+ }
+
+ pub fn open_blob(&mut self, digest: &Digest) -> Option<TarEntryReader<R>> {
+ self.archive.get_inner_file(get_blob_path(digest))
+ }
+
+ pub fn image_manifest(
+ &mut self,
+ architecture: &Arch,
+ ) -> Option<oci_spec::Result<ImageManifest>> {
+ match self.image_index.manifests().iter().find(|&x| {
+ x.media_type() == &MediaType::ImageManifest
+ && x.platform()
+ .as_ref()
+ .is_none_or(|platform| platform.architecture() == architecture)
+ }) {
+ Some(descriptor) => match self.open_blob(&descriptor.digest().clone()) {
+ Some(image_manifest_file) => Some(ImageManifest::from_reader(image_manifest_file)),
+ None => Some(Err(OciSpecError::Other(
+ "Image manifest mentioned in image index is missing".into(),
+ ))),
+ },
+ None => None,
+ }
+ }
+}
+
+fn get_blob_path(digest: &Digest) -> PathBuf {
+ let algorithm = digest.algorithm().as_ref();
+ let digest = digest.digest();
+ let mut path = PathBuf::from("blobs");
+ path.push(algorithm);
+ path.push(digest);
+ path
+}
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH proxmox-perl-rs v2 02/11] add Perl mapping for OCI container image parser/extractor
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox v2 01/11] add proxmox-oci crate Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 03/11] config: whitelist lxc.init.cwd Filip Schauer
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
pve-rs/Cargo.toml | 2 ++
pve-rs/Makefile | 1 +
pve-rs/debian/control | 2 ++
pve-rs/src/lib.rs | 1 +
pve-rs/src/oci.rs | 20 ++++++++++++++++++++
5 files changed, 26 insertions(+)
create mode 100644 pve-rs/src/oci.rs
diff --git a/pve-rs/Cargo.toml b/pve-rs/Cargo.toml
index c7f11a3..00624c3 100644
--- a/pve-rs/Cargo.toml
+++ b/pve-rs/Cargo.toml
@@ -20,6 +20,7 @@ hex = "0.4"
http = "1"
libc = "0.2"
nix = "0.29"
+oci-spec = "0.8.1"
openssl = "0.10.40"
serde = "1.0"
serde_bytes = "0.11"
@@ -37,6 +38,7 @@ proxmox-http = { version = "1", features = ["client-sync", "client-trait"] }
proxmox-http-error = "1"
proxmox-log = "1"
proxmox-notify = { version = "1", features = ["pve-context"] }
+proxmox-oci = "0.1.0"
proxmox-openid = "1"
proxmox-resource-scheduling = "1"
proxmox-shared-cache = "1"
diff --git a/pve-rs/Makefile b/pve-rs/Makefile
index afe792a..d6a667f 100644
--- a/pve-rs/Makefile
+++ b/pve-rs/Makefile
@@ -27,6 +27,7 @@ PERLMOD_GENPACKAGE := /usr/lib/perlmod/genpackage.pl \
PERLMOD_PACKAGES := \
PVE::RS::Firewall::SDN \
+ PVE::RS::OCI \
PVE::RS::OpenId \
PVE::RS::ResourceScheduling::Static \
PVE::RS::TFA
diff --git a/pve-rs/debian/control b/pve-rs/debian/control
index 9e424ec..243b428 100644
--- a/pve-rs/debian/control
+++ b/pve-rs/debian/control
@@ -10,6 +10,7 @@ Build-Depends: cargo:native <!nocheck>,
librust-http-1+default-dev (>= 0.2.7-~~),
librust-libc-0.2+default-dev,
librust-nix-0.29+default-dev,
+ librust-oci-spec-0.8+default-dev (>= 0.8.1-~~),
librust-openssl-0.10+default-dev (>= 0.10.40-~~),
librust-perlmod-0.14+default-dev (>= 0.13.5-~~),
librust-perlmod-0.14+exporter-dev (>= 0.13.5-~~),
@@ -24,6 +25,7 @@ Build-Depends: cargo:native <!nocheck>,
librust-proxmox-log-1+default-dev,
librust-proxmox-notify-1+default-dev (>= 0.5.4),
librust-proxmox-notify-1+pve-context-dev,
+ librust-proxmox-oci-0.1+default-dev,
librust-proxmox-openid-1+default-dev (>= 0.10.4-~~),
librust-proxmox-resource-scheduling-1+default-dev,
librust-proxmox-shared-cache-1+default-dev,
diff --git a/pve-rs/src/lib.rs b/pve-rs/src/lib.rs
index bb979b9..1fe9e77 100644
--- a/pve-rs/src/lib.rs
+++ b/pve-rs/src/lib.rs
@@ -12,6 +12,7 @@ use proxmox_notify::{Config, Notification, Severity};
mod common;
pub mod firewall;
+pub mod oci;
pub mod openid;
pub mod bindings;
diff --git a/pve-rs/src/oci.rs b/pve-rs/src/oci.rs
new file mode 100644
index 0000000..dd556aa
--- /dev/null
+++ b/pve-rs/src/oci.rs
@@ -0,0 +1,20 @@
+#[perlmod::package(name = "PVE::RS::OCI")]
+mod export {
+ use anyhow::{Error, bail};
+ use oci_spec::image::Config;
+ use proxmox_oci::{ParseError, ProxmoxOciError};
+
+ #[export]
+ pub fn parse_and_extract_image(
+ oci_tar_path: &str,
+ rootfs_path: &str,
+ ) -> Result<Option<Config>, Error> {
+ match proxmox_oci::parse_and_extract_image(oci_tar_path, rootfs_path) {
+ Ok(config) => Ok(Some(config.unwrap_or_default())),
+ Err(err) => match err {
+ ProxmoxOciError::ParseError(ParseError::NotAnOciImage(_)) => Ok(None),
+ err => bail!("{err}"),
+ },
+ }
+ }
+}
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 03/11] config: whitelist lxc.init.cwd
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox v2 01/11] add proxmox-oci crate Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox-perl-rs v2 02/11] add Perl mapping for OCI container image parser/extractor Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 04/11] add support for OCI images as container templates Filip Schauer
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
This parameter allows setting the working directory of the init process
in the container. This can be used by containers created from an OCI
image, that specifies a custom working directory.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC/Config.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index 0740e8c..49067ea 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -641,6 +641,7 @@ my $valid_lxc_conf_keys = {
'lxc.signal.reboot' => 1,
'lxc.signal.stop' => 1,
'lxc.init.cmd' => 1,
+ 'lxc.init.cwd' => 1,
'lxc.pty.max' => 1,
'lxc.console.logfile' => 1,
'lxc.console.path' => 1,
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 04/11] add support for OCI images as container templates
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (2 preceding siblings ...)
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 03/11] config: whitelist lxc.init.cwd Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 05/11] config: add entrypoint parameter Filip Schauer
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
This aims to add basic support for the Open Container Initiative image
format according to the specification. [0]
[0] https://github.com/opencontainers/image-spec/blob/main/spec.md
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/API2/LXC.pm | 64 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 61 insertions(+), 3 deletions(-)
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 5c6ee57..3b0ea35 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -19,9 +19,11 @@ use PVE::Storage;
use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::ReplicationConfig;
+use PVE::RS::OCI;
use PVE::LXC;
use PVE::LXC::Create;
use PVE::LXC::Migrate;
+use PVE::LXC::Namespaces;
use PVE::GuestHelpers;
use PVE::VZDump::Plugin;
use PVE::API2::LXC::Config;
@@ -484,9 +486,65 @@ __PACKAGE__->register_method({
eval {
my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1);
$bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit);
- print "restoring '$archive' now..\n"
- if $restore && $archive ne '-';
- PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
+ my $oci_config;
+
+ if ($restore && $archive ne '-') {
+ print "restoring '$archive' now..\n";
+ } else {
+ # Try interpreting the file as an OCI image first.
+ # If it fails, treat it as an LXC template instead.
+ my $archivepath = PVE::Storage::abs_filesystem_path($storage_cfg, $archive);
+ if ($archivepath =~ /\.tar$/) {
+ my ($id_map, $root_uid, $root_gid) = PVE::LXC::parse_id_maps($conf);
+ $oci_config = PVE::LXC::Namespaces::run_in_userns(sub {
+ PVE::RS::OCI::parse_and_extract_image($archivepath, $rootdir)
+ }, $id_map);
+ }
+ }
+
+ if (defined($oci_config)) {
+ # OCI image extracted successfully
+
+ # Set the entrypoint and arguments if specified by the OCI image
+ my @init_cmd = ();
+ push(@init_cmd, @{$oci_config->{Entrypoint}}) if $oci_config->{Entrypoint};
+ push(@init_cmd, @{$oci_config->{Cmd}}) if $oci_config->{Cmd};
+ if (@init_cmd) {
+ my $init_cmd_str = shift(@init_cmd);
+ if (@init_cmd) {
+ $init_cmd_str .= ' ';
+ $init_cmd_str .= join(' ', map {
+ my $s = $_; $s =~ s/"/\\"/g; qq{"$_"}
+ } @init_cmd);
+ }
+ push @{$conf->{lxc}}, ['lxc.init.cmd', $init_cmd_str];
+ # An entrypoint other than /sbin/init breaks the tty console mode.
+ # This is fixed by setting cmode: console
+ $conf->{cmode} = 'console';
+ }
+
+ push @{$conf->{lxc}}, ['lxc.init.cwd', $oci_config->{WorkingDir}]
+ if ($oci_config->{WorkingDir});
+
+ if (my $envs = $oci_config->{Env}) {
+ for my $env (@{$envs}) {
+ push @{$conf->{lxc}}, ['lxc.environment', $env];
+ }
+ }
+
+ my $stop_signal = $oci_config->{StopSignal} // "SIGTERM";
+ push @{$conf->{lxc}}, ['lxc.signal.halt', $stop_signal];
+ } else {
+ # Not an OCI image, so restore it as an LXC image instead
+ PVE::LXC::Create::restore_archive(
+ $storage_cfg,
+ $archive,
+ $rootdir,
+ $conf,
+ $ignore_unpack_errors,
+ $bwlimit
+ );
+ }
if ($restore) {
print "merging backed-up and given configuration..\n";
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 05/11] config: add entrypoint parameter
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (3 preceding siblings ...)
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 04/11] add support for OCI images as container templates Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 06/11] configure static IP in LXC config for custom entrypoint Filip Schauer
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/API2/LXC.pm | 2 +-
src/PVE/LXC.pm | 2 ++
src/PVE/LXC/Config.pm | 12 ++++++++++++
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 3b0ea35..cf93778 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -517,7 +517,7 @@ __PACKAGE__->register_method({
my $s = $_; $s =~ s/"/\\"/g; qq{"$_"}
} @init_cmd);
}
- push @{$conf->{lxc}}, ['lxc.init.cmd', $init_cmd_str];
+ $conf->{entrypoint} = $init_cmd_str;
# An entrypoint other than /sbin/init breaks the tty console mode.
# This is fixed by setting cmode: console
$conf->{cmode} = 'console';
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 2b9f0cf..b2be27e 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -794,6 +794,8 @@ sub update_lxc_config {
$raw .= "lxc.rootfs.path = $dir/rootfs\n";
+ $raw .= "lxc.init.cmd = $conf->{entrypoint}\n" if defined($conf->{entrypoint});
+
foreach my $k (sort keys %$conf) {
next if $k !~ m/^net(\d+)$/;
my $ind = $1;
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index 49067ea..d7d8b6a 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -591,6 +591,12 @@ my $confdesc = {
enum => ['shell', 'console', 'tty'],
default => 'tty',
},
+ entrypoint => {
+ optional => 1,
+ type => 'string',
+ description => "Absolute path from container rootfs to the binary to use as init.",
+ default => '/sbin/init',
+ },
protection => {
optional => 1,
type => 'boolean',
@@ -1745,6 +1751,12 @@ sub get_cmode {
return $conf->{cmode} // $confdesc->{cmode}->{default};
}
+sub get_entrypoint {
+ my ($class, $conf) = @_;
+
+ return $conf->{entrypoint} // $confdesc->{entrypoint}->{default};
+}
+
sub valid_volume_keys {
my ($class, $reverse) = @_;
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 06/11] configure static IP in LXC config for custom entrypoint
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (4 preceding siblings ...)
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 05/11] config: add entrypoint parameter Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 07/11] setup: debian: create /etc/network path if missing Filip Schauer
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
When a container uses the default `/sbin/init` entrypoint, network
interface configuration is usually managed by processes within the
container. However, containers with a different entrypoint might not
have any internal network management process. Consequently, IP addresses
might not be assigned.
This change ensures that a static IP address is explicitly set in the
LXC config for the container.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC.pm | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index b2be27e..0131ac3 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -818,6 +818,18 @@ sub update_lxc_config {
if ($lxc_major >= 4) {
$raw .= "lxc.net.$ind.script.up = /usr/share/lxc/lxcnetaddbr\n";
}
+
+ if (!defined($d->{link_down}) || $d->{link_down} != 1
+ && PVE::LXC::Config->get_entrypoint($conf) ne "/sbin/init"
+ ) {
+ $raw .= "lxc.net.$ind.ipv4.address = $d->{ip}\n"
+ if defined($d->{ip}) && $d->{ip} !~ /^(dhcp|manual)$/;
+ $raw .= "lxc.net.$ind.ipv4.gateway = $d->{gw}\n" if defined($d->{gw});
+ $raw .= "lxc.net.$ind.ipv6.address = $d->{ip6}\n"
+ if defined($d->{ip6}) && $d->{ip6} !~ /^(auto|dhcp|manual)$/;
+ $raw .= "lxc.net.$ind.ipv6.gateway = $d->{gw6}\n" if defined($d->{gw6});
+ $raw .= "lxc.net.$ind.flags = up\n";
+ }
}
my $had_cpuset = 0;
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 07/11] setup: debian: create /etc/network path if missing
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (5 preceding siblings ...)
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 06/11] configure static IP in LXC config for custom entrypoint Filip Schauer
@ 2025-06-11 14:48 ` Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH container v2 08/11] setup: recursively mkdir /etc/systemd/{network, system-preset} Filip Schauer
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:48 UTC (permalink / raw)
To: pve-devel
This prevents an error during Debian container setup when the
/etc/network directory is missing. This fixes container creation from
Debian based OCI images.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC/Setup/Debian.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/PVE/LXC/Setup/Debian.pm b/src/PVE/LXC/Setup/Debian.pm
index 8f7f524..b607c44 100644
--- a/src/PVE/LXC/Setup/Debian.pm
+++ b/src/PVE/LXC/Setup/Debian.pm
@@ -418,6 +418,7 @@ sub setup_network {
$interfaces;
}
+ $self->ct_make_path('/etc/network');
$self->ct_file_set_contents($filename, $interfaces);
}
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 08/11] setup: recursively mkdir /etc/systemd/{network, system-preset}
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (6 preceding siblings ...)
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 07/11] setup: debian: create /etc/network path if missing Filip Schauer
@ 2025-06-11 14:49 ` Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH container v2 09/11] manage DHCP for containers with custom entrypoint Filip Schauer
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:49 UTC (permalink / raw)
To: pve-devel
Ensure that both /etc/systemd/network and /etc/systemd/system-preset
exist before writing files into them. This fixes container creation from
the docker.io/fedora & docker.io/ubuntu OCI images.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC/Setup/Base.pm | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/PVE/LXC/Setup/Base.pm b/src/PVE/LXC/Setup/Base.pm
index bb084af..7119e90 100644
--- a/src/PVE/LXC/Setup/Base.pm
+++ b/src/PVE/LXC/Setup/Base.pm
@@ -306,6 +306,7 @@ DATA
$data .= "IPv6AcceptRA = $accept_ra\n";
$data .= $routes if $routes;
+ $self->ct_make_path('/etc/systemd/network');
$self->ct_file_set_contents($filename, $data);
}
}
@@ -345,7 +346,7 @@ sub setup_systemd_preset {
}
}
- $self->ct_mkdir('/etc/systemd/system-preset', 0755);
+ $self->ct_make_path('/etc/systemd/system-preset');
$self->ct_file_set_contents(
'/etc/systemd/system-preset/00-pve.preset',
$preset_data,
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH container v2 09/11] manage DHCP for containers with custom entrypoint
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (7 preceding siblings ...)
2025-06-11 14:49 ` [pve-devel] [PATCH container v2 08/11] setup: recursively mkdir /etc/systemd/{network, system-preset} Filip Schauer
@ 2025-06-11 14:49 ` Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH storage v2 10/11] allow .tar container templates Filip Schauer
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:49 UTC (permalink / raw)
To: pve-devel
Containers that do not use the default `/sbin/init` entrypoint may lack
in‑container network management. A previous commit already handles
static IP addresses. Now this commit also handles DHCP. This is done
using a `dhclient` process for each network interface.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/LXC.pm | 74 ++++++++++++++++++++++++++++++++++++++++---
src/PVE/LXC/Config.pm | 6 +++-
2 files changed, 74 insertions(+), 6 deletions(-)
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 0131ac3..e91b53a 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -1004,6 +1004,8 @@ sub vm_stop_cleanup {
PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
};
warn $@ if $@; # avoid errors - just warn
+
+ kill_dhclients($vmid, '*') if (PVE::LXC::Config->get_entrypoint($conf) ne "/sbin/init");
}
sub net_tap_plug : prototype($$) {
@@ -1189,6 +1191,34 @@ sub get_interfaces {
return $res;
}
+sub manage_dhclient {
+ my ($action, $vmid, $ipversion, $eth, $rootdir) = @_;
+
+ File::Path::make_path("/var/lib/lxc/$vmid/hook") if $action eq 'start';
+ my $pidfile = "/var/lib/lxc/$vmid/hook/dhclient$ipversion-$eth.pid";
+ my $leasefile = "/var/lib/lxc/$vmid/hook/dhclient$ipversion-$eth.leases";
+ my $scriptfile = '/usr/share/lxc/hooks/dhclient-script';
+ PVE::Tools::run_command([
+ 'lxc-attach', '-n', $vmid, '-s', 'NETWORK|UTSNAME', '--',
+ 'aa-exec', '-p', 'unconfined',
+ '/sbin/dhclient', $action eq 'start' ? '-1' : '-r', "-$ipversion",
+ '-pf', $pidfile, '-lf', $leasefile, '-e', "ROOTFS=$rootdir", '-sf', $scriptfile, $eth
+ ]);
+}
+
+sub kill_dhclients {
+ my ($vmid, $eth) = @_;
+
+ foreach my $pidfile (glob("/var/lib/lxc/$vmid/hook/dhclient*-$eth.pid")) {
+ my $pid = eval { file_get_contents($pidfile) };
+ if (!$@) {
+ chomp $pid;
+ kill 9, $pid if ($pid =~ m/^\d+$/);
+ unlink($pidfile);
+ }
+ }
+}
+
sub update_ipconfig {
my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
@@ -1223,11 +1253,21 @@ sub update_ipconfig {
# step 1: add new IP, if this fails we cancel
my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
- if ($change_ip && $is_real_ip) {
- eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
- if (my $err = $@) {
- warn $err;
- return;
+ if ($change_ip) {
+ if (PVE::LXC::Config->get_entrypoint($conf) ne "/sbin/init") {
+ if ($newip && $newip eq 'dhcp') {
+ manage_dhclient('start', $vmid, $ipversion, $eth, $rootdir);
+ } elsif ($oldip && $oldip eq 'dhcp') {
+ manage_dhclient('stop', $vmid, $ipversion, $eth, $rootdir);
+ }
+ }
+
+ if ($is_real_ip) {
+ eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
+ if (my $err = $@) {
+ warn $err;
+ return;
+ }
}
}
@@ -2707,6 +2747,30 @@ sub vm_start {
}
PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');
+ my @dhcpv4_interfaces = ();
+ my @dhcpv6_interfaces = ();
+ foreach my $k (sort keys %$conf) {
+ next if $k !~ m/^net(\d+)$/;
+ my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
+ push @dhcpv4_interfaces, $d->{name} if $d->{ip} && $d->{ip} eq 'dhcp';
+ push @dhcpv6_interfaces, $d->{name} if $d->{ip6} && $d->{ip6} eq 'dhcp';
+ }
+
+ my $pid = PVE::LXC::find_lxc_pid($vmid);
+ my $rootdir = "/proc/$pid/root";
+
+ if (PVE::LXC::Config->get_entrypoint($conf) ne "/sbin/init") {
+ foreach my $eth (@dhcpv4_interfaces) {
+ eval { manage_dhclient('start', $vmid, 4, $eth, $rootdir) };
+ PVE::RESTEnvironment::log_warn("DHCP failed - $@") if $@;
+ }
+
+ foreach my $eth (@dhcpv6_interfaces) {
+ eval { manage_dhclient('stop', $vmid, 6, $eth, $rootdir) };
+ PVE::RESTEnvironment::log_warn("DHCP failed - $@") if $@;
+ }
+ }
+
return;
}
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index d7d8b6a..854e711 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -1490,9 +1490,13 @@ sub vmconfig_hotplug_pending {
$cgroup->change_cpu_shares(undef);
} elsif ($opt =~ m/^net(\d)$/) {
my $netid = $1;
+ my $net = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
+ if (PVE::LXC::Config->get_entrypoint($conf) ne "/sbin/init") {
+ PVE::LXC::kill_dhclients($vmid, $net->{name});
+ }
+
PVE::Network::veth_delete("veth${vmid}i$netid");
if ($have_sdn) {
- my $net = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
print "delete ips from $opt\n";
eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{hwaddr}, $conf->{hostname}) };
warn $@ if $@;
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH storage v2 10/11] allow .tar container templates
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (8 preceding siblings ...)
2025-06-11 14:49 ` [pve-devel] [PATCH container v2 09/11] manage DHCP for containers with custom entrypoint Filip Schauer
@ 2025-06-11 14:49 ` Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH manager v2 11/11] ui: storage upload: accept *.tar files as vztmpl Filip Schauer
2025-06-17 8:01 ` [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Christoph Heiss
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:49 UTC (permalink / raw)
To: pve-devel
This is needed for OCI container images bundled as tar files, as
generated by `docker save`. OCI images do not need additional
compression, since the content is usually compressed already.
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
src/PVE/Storage.pm | 2 +-
src/PVE/Storage/Plugin.pm | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm
index 3962077..0d43df1 100755
--- a/src/PVE/Storage.pm
+++ b/src/PVE/Storage.pm
@@ -117,7 +117,7 @@ PVE::Storage::Plugin->init();
our $ISO_EXT_RE_0 = qr/\.(?:iso|img)/i;
-our $VZTMPL_EXT_RE_1 = qr/\.tar\.(gz|xz|zst|bz2)/i;
+our $VZTMPL_EXT_RE_1 = qr/\.tar(?:\.(gz|xz|zst|bz2))?/i;
our $BACKUP_EXT_RE_2 = qr/\.(tgz|(?:tar|vma)(?:\.(${\PVE::Storage::Plugin::COMPRESSOR_RE}))?)/;
diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm
index 1d8a8f9..76ffdb6 100644
--- a/src/PVE/Storage/Plugin.pm
+++ b/src/PVE/Storage/Plugin.pm
@@ -1429,7 +1429,7 @@ my $get_subdir_files = sub {
} elsif ($tt eq 'vztmpl') {
next if $fn !~ m!/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!;
- $info = { volid => "$sid:vztmpl/$1", format => "t$2" };
+ $info = { volid => "$sid:vztmpl/$1", format => $2 ? "t$2" : "tar" };
} elsif ($tt eq 'backup') {
next if $fn !~ m!/([^/]+$PVE::Storage::BACKUP_EXT_RE_2)$!;
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pve-devel] [PATCH manager v2 11/11] ui: storage upload: accept *.tar files as vztmpl
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (9 preceding siblings ...)
2025-06-11 14:49 ` [pve-devel] [PATCH storage v2 10/11] allow .tar container templates Filip Schauer
@ 2025-06-11 14:49 ` Filip Schauer
2025-06-17 8:01 ` [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Christoph Heiss
11 siblings, 0 replies; 13+ messages in thread
From: Filip Schauer @ 2025-06-11 14:49 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
---
www/manager6/window/UploadToStorage.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/www/manager6/window/UploadToStorage.js b/www/manager6/window/UploadToStorage.js
index f6cad0ec..cfd0a949 100644
--- a/www/manager6/window/UploadToStorage.js
+++ b/www/manager6/window/UploadToStorage.js
@@ -11,7 +11,7 @@ Ext.define('PVE.window.UploadToStorage', {
acceptedExtensions: {
'import': ['.ova', '.qcow2', '.raw', '.vmdk'],
iso: ['.img', '.iso'],
- vztmpl: ['.tar.gz', '.tar.xz', '.tar.zst'],
+ vztmpl: ['.tar', '.tar.gz', '.tar.xz', '.tar.zst'],
},
// accepted for file selection, will be renamed to real extension
--
2.39.5
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
` (10 preceding siblings ...)
2025-06-11 14:49 ` [pve-devel] [PATCH manager v2 11/11] ui: storage upload: accept *.tar files as vztmpl Filip Schauer
@ 2025-06-17 8:01 ` Christoph Heiss
11 siblings, 0 replies; 13+ messages in thread
From: Christoph Heiss @ 2025-06-17 8:01 UTC (permalink / raw)
To: Filip Schauer; +Cc: Proxmox VE development discussion
pve-container changes now need a rebase due to the perltidy
re-formatting.
Tested the series using the same setup as last time. Tested the
following images, exported with podman:
- docker.io/library/nginx:mainline-alpine
- docker.io/library/nginx:mainline-bookworm
- ghcr.io/dani-garcia/vaultwarden:1.34.1-alpine
- docker.io/library/node:24-bookworm
Can confirm that all of these started up correctly and are
reachable/usable afterwards and that the problem with the entrypoint has
been fixed.
I also test with `ghcr.io/nixos/nix:latest`, which interestingly fails
to start with
DEBUG utils - ../src/lxc/utils.c:run_buffer:560 - Script exec /usr/share/lxcfs/lxc.mount.hook 107 lxc mount produced output: /usr/share/lxcfs/lxc.mount.hook: 15: readlink: Permission denied
Not sure what is going on there, but I don't think it's directly related
to this series, rather just some OCI/Nix weirdness.
On Wed Jun 11, 2025 at 4:48 PM CEST, Filip Schauer wrote:
> Add basic support for OCI (Open Container Initiative) images [0] as
> container templates.
>
> An OCI image can be for example obtained from Docker Hub:
>
> Either using Docker:
>
> ```
> $ docker pull httpd
> $ docker save httpd > httpd.tar
> ```
>
> Or using Podman:
> When using Podman, the format needs to be explicitly specified,
> otherwise it defaults to docker-archive.
>
> ```
> $ podman pull httpd
> $ podman save --format=oci-archive httpd > httpd.tar
> ```
>
> The tarball can be uploaded to a storage as a container template and
> then used during container creation. It is automatically detected that
> the container template is an OCI image. The resulting container still
> uses the existing LXC framework.
Needs to documented in pve-docs too, this paragraph would already make a
great start tbh.
>
[..]
> # Build & install order:
>
> OCI image support:
> 1. proxmox
> 2. proxmox-perl-rs
> 3. pve-container
>
> .tar container template support:
> 1. pve-storage
> 2. pve-manager
Should be mentioned here (and ideally, on the respective pve-container
patch(es) as well) that pve-container depends on libpve-rs-perl after
this series, i.e. an appropriate entry needs to be added to d/control.
>
> [0] https://github.com/opencontainers/image-spec/blob/main/spec.md
> [1] https://lore.proxmox.com/pve-devel/20250606103719.533030-2-c.heiss@proxmox.com/
>
> Changed since v1:
> * Fix entrypoint command missing Cmd
> * Set lxc.signal.halt according to StopSignal (Fixes container shutdown)
> * setup: Ensure that both /etc/systemd/network and
> /etc/systemd/system-preset exist before writing files into them.
> * ui: storage upload: accept *.tar files as vztmpl
> * proxmox-perl-rs: rebase on latest master (3d9806cb3c7f)
> * proxmox-perl-rs: add new dependencies to debian/control
> * proxmox-oci: refactor errors and use `thiserror` to avoid boilerplate
Please also mention changes in the notes of each patch - makes reviewing
a lot easier!
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2025-06-17 8:02 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-11 14:48 [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox v2 01/11] add proxmox-oci crate Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH proxmox-perl-rs v2 02/11] add Perl mapping for OCI container image parser/extractor Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 03/11] config: whitelist lxc.init.cwd Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 04/11] add support for OCI images as container templates Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 05/11] config: add entrypoint parameter Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 06/11] configure static IP in LXC config for custom entrypoint Filip Schauer
2025-06-11 14:48 ` [pve-devel] [PATCH container v2 07/11] setup: debian: create /etc/network path if missing Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH container v2 08/11] setup: recursively mkdir /etc/systemd/{network, system-preset} Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH container v2 09/11] manage DHCP for containers with custom entrypoint Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH storage v2 10/11] allow .tar container templates Filip Schauer
2025-06-11 14:49 ` [pve-devel] [PATCH manager v2 11/11] ui: storage upload: accept *.tar files as vztmpl Filip Schauer
2025-06-17 8:01 ` [pve-devel] [PATCH container/manager/proxmox{, -perl-rs}/storage v2 00/11] support OCI images as container templates Christoph Heiss
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal