diff --git a/packages/orchestrator/README.md b/packages/orchestrator/README.md index 5a56bbb571..97af8134e5 100644 --- a/packages/orchestrator/README.md +++ b/packages/orchestrator/README.md @@ -148,5 +148,5 @@ Automatically set in local mode. Set before running to override: ## Limitations -- Custom template builds support base images from the declared distro families: **Debian/Ubuntu** (apt), the **RHEL family** — Fedora, CentOS Stream, Rocky, Alma — (dnf/microdnf/yum), **TencentOS Server** 3+ and **OpenCloudOS** 8+ (dnf/microdnf/yum; TencentOS Server 2.x repos lack socat, fuse3, nftables and jq, so its builds fail at package install), **Arch** (pacman), and **Alpine** (apk, OpenRC). The distro is resolved from the image's `/etc/os-release` `ID` — never by probing for package managers. Images without an os-release identity (distroless, scratch) or from an undeclared family are rejected fast, with a build-log error naming the reason and the supported families (see `pkg/template/build/phases/base/distro`). Minimal/restricted-repo images may fail provisioning if their repos don't carry the required packages; the failure is surfaced in the build log. +- Custom template builds support base images from the declared distro families: **Debian/Ubuntu** (apt), the **RHEL family** — Fedora, CentOS Stream, Rocky, Alma — (dnf/microdnf/yum), **TencentOS Server** 3+ and **OpenCloudOS** 8+ (dnf/microdnf/yum; TencentOS Server 2.x repos lack socat, fuse3, nftables and jq, so its builds fail at package install), **UnionTech OS (UOS) Server** 20 (dnf/microdnf/yum; its two image lineages name the account-tools package differently, shadow vs shadow-utils, so the profile installs neither and relies on the base image), **Arch** (pacman), and **Alpine** (apk, OpenRC). The distro is resolved from the image's `/etc/os-release` `ID` — never by probing for package managers. Images without an os-release identity (distroless, scratch) or from an undeclared family are rejected fast, with a build-log error naming the reason and the supported families (see `pkg/template/build/phases/base/distro`). Minimal/restricted-repo images may fail provisioning if their repos don't carry the required packages; the failure is surfaced in the build log. - Sandboxes always boot the kernel E2B supplies, never one from the base image: `/lib/modules` is empty, no kernel modules can be loaded, and SELinux is disabled on the kernel command line (`selinux=0`), so a base image that ships an SELinux policy boots without it. `kernel-devel` from a distro's repos resolves against a kernel that isn't running. **RHEL** (`rhel`, incl. UBI), **Oracle Linux** (`ol`) and **Amazon Linux** (`amzn`) are therefore not accepted even though they are RPM/dnf images — they are chosen for kABI, signed kernel modules and UEK, which this cannot honour. The community rebuilds above are supported because they are chosen for the userland. diff --git a/packages/orchestrator/pkg/template/build/phases/base/distro/distro.go b/packages/orchestrator/pkg/template/build/phases/base/distro/distro.go index 8eb3e1ec19..9b45b74936 100644 --- a/packages/orchestrator/pkg/template/build/phases/base/distro/distro.go +++ b/packages/orchestrator/pkg/template/build/phases/base/distro/distro.go @@ -124,6 +124,37 @@ var Profiles = []Profile{ CABundle: "/etc/ssl/certs/ca-certificates.crt", CARefresh: `update-ca-trust extract && ln -sf /etc/pki/tls/certs/ca-bundle.crt "$E2B_CA_BUNDLE"`, }, + { + // UnionTech OS (统信 UOS) Server 20 is an RPM/dnf, systemd distro. Its + // /etc/os-release declares ID=uos and no ID_LIKE, so without this profile + // a UOS base falls through to the unsupported-distro error. + // + // Verified against two independent UOS Server 20 images + // (macrosan/uos:v20-1050 and ixuzhi/uos-server:1070a): both are + // RHEL-shaped, but the two lineages name the account tools package + // differently — one ships shadow, the other shadow-utils, and listing + // either aborts the dnf transaction on the image that lacks it. Both + // base images already ship useradd/usermod, so this profile installs + // neither and relies on the base, as the Debian family does; passwd is + // still installed and resolves on both. + Key: "uos", + Init: InitSystemd, + IDs: []string{"uos"}, + Packages: []string{ + "systemd", "passwd", "openssh-server", "sudo", "chrony", + "socat", "curl", "ca-certificates", "fuse3", "iptables", "git", + "nfs-utils", "less", "nftables", "iputils", "jq", "bash", "tar", + "iproute", "findutils", + }, + PkgQueryBody: `rpm -q "$1" >/dev/null 2>&1`, + PkgInstall: `dnf -y install --allowerasing "$@" || microdnf -y install "$@" || yum -y install "$@"`, + InitBinary: "/usr/lib/systemd/systemd", + TimeSyncUnit: "chronyd", + SSHUnit: "sshd", + AdminGroup: "wheel", + CABundle: "/etc/ssl/certs/ca-certificates.crt", + CARefresh: `update-ca-trust extract && ln -sf /etc/pki/tls/certs/ca-bundle.crt "$E2B_CA_BUNDLE"`, + }, { Key: "arch", Init: InitSystemd, diff --git a/packages/orchestrator/pkg/template/build/phases/base/distro/distro_test.go b/packages/orchestrator/pkg/template/build/phases/base/distro/distro_test.go index e2b6a7da97..a74f1e98d0 100644 --- a/packages/orchestrator/pkg/template/build/phases/base/distro/distro_test.go +++ b/packages/orchestrator/pkg/template/build/phases/base/distro/distro_test.go @@ -1,6 +1,7 @@ package distro import ( + "slices" "crypto/sha256" "encoding/hex" "strings" @@ -339,3 +340,33 @@ func TestRejectedIDsAreNotReachableViaIDLike(t *testing.T) { t.Error("the rejected-id guard must precede the ID_LIKE fallback") } } + +// UOS Server 20 declares ID=uos and no ID_LIKE. Verified against +// macrosan/uos:v20-1050 and ixuzhi/uos-server:1070a: RHEL-shaped, but the two +// lineages name the account-tools package differently (shadow vs shadow-utils), +// so the profile installs neither — the base image ships useradd — and must not +// list either, or dnf aborts on the image lacking it. +func TestUOSProfile(t *testing.T) { + t.Parallel() + u := profileByKey(t, "uos") + if !slices.Contains(u.IDs, "uos") { + t.Errorf("uos id must be matched, got IDs=%v", u.IDs) + } + if slices.Contains(u.Packages, "shadow") || slices.Contains(u.Packages, "shadow-utils") { + t.Errorf("uos must install neither shadow nor shadow-utils (lineage-split name); got %v", u.Packages) + } + if !slices.Contains(u.Packages, "passwd") { + t.Error("uos must still install passwd") + } + if u.InitBinary != "/usr/lib/systemd/systemd" { + t.Errorf("uos init path wrong: %s", u.InitBinary) + } + for _, want := range []string{"dnf -y install --allowerasing", "|| microdnf -y install", "|| yum -y install"} { + if !strings.Contains(u.PkgInstall, want) { + t.Errorf("uos install missing %q: %s", want, u.PkgInstall) + } + } + if !strings.Contains(ShellSelector(), "uos)") { + t.Error("selector must have a uos arm") + } +} diff --git a/packages/orchestrator/pkg/template/build/phases/base/distro/parity_test.go b/packages/orchestrator/pkg/template/build/phases/base/distro/parity_test.go index 42238e65ae..6b26eff286 100644 --- a/packages/orchestrator/pkg/template/build/phases/base/distro/parity_test.go +++ b/packages/orchestrator/pkg/template/build/phases/base/distro/parity_test.go @@ -22,6 +22,7 @@ var capabilities = []struct { "debian": {"systemd", "systemd-sysv"}, "rhel": {"systemd"}, "tencentos": {"systemd"}, + "uos": {"systemd"}, "arch": {"systemd"}, "alpine": {"openrc"}, }}, @@ -30,6 +31,7 @@ var capabilities = []struct { "debian": {}, "rhel": {"bash"}, "tencentos": {"bash"}, + "uos": {"bash"}, "arch": {"bash"}, "alpine": {"bash"}, }}, @@ -38,6 +40,7 @@ var capabilities = []struct { "debian": {}, "rhel": {"shadow-utils", "passwd"}, "tencentos": {"shadow-utils", "passwd"}, + "uos": {"passwd"}, "arch": {"shadow"}, "alpine": {"shadow"}, }}, @@ -45,6 +48,7 @@ var capabilities = []struct { "debian": {"openssh-server"}, "rhel": {"openssh-server"}, "tencentos": {"openssh-server"}, + "uos": {"openssh-server"}, "arch": {"openssh"}, "alpine": {"openssh"}, }}, @@ -52,6 +56,7 @@ var capabilities = []struct { "debian": {"sudo"}, "rhel": {"sudo"}, "tencentos": {"sudo"}, + "uos": {"sudo"}, "arch": {"sudo"}, "alpine": {"sudo"}, }}, @@ -59,6 +64,7 @@ var capabilities = []struct { "debian": {"chrony"}, "rhel": {"chrony"}, "tencentos": {"chrony"}, + "uos": {"chrony"}, "arch": {"chrony"}, "alpine": {"chrony"}, }}, @@ -66,6 +72,7 @@ var capabilities = []struct { "debian": {"ca-certificates"}, "rhel": {"ca-certificates"}, "tencentos": {"ca-certificates"}, + "uos": {"ca-certificates"}, "arch": {"ca-certificates"}, "alpine": {"ca-certificates"}, }}, @@ -73,6 +80,7 @@ var capabilities = []struct { "debian": {"fuse3"}, "rhel": {"fuse3"}, "tencentos": {"fuse3"}, + "uos": {"fuse3"}, "arch": {"fuse3"}, "alpine": {"fuse3"}, }}, @@ -80,6 +88,7 @@ var capabilities = []struct { "debian": {"nfs-common"}, "rhel": {"nfs-utils"}, "tencentos": {"nfs-utils"}, + "uos": {"nfs-utils"}, "arch": {"nfs-utils"}, "alpine": {"nfs-utils"}, }}, @@ -87,6 +96,7 @@ var capabilities = []struct { "debian": {"iptables", "nftables"}, "rhel": {"iptables", "nftables"}, "tencentos": {"iptables", "nftables"}, + "uos": {"iptables", "nftables"}, "arch": {"iptables", "nftables"}, "alpine": {"iptables", "nftables"}, }}, @@ -94,6 +104,7 @@ var capabilities = []struct { "debian": {"git"}, "rhel": {"git"}, "tencentos": {"git"}, + "uos": {"git"}, "arch": {"git"}, "alpine": {"git"}, }}, @@ -101,6 +112,7 @@ var capabilities = []struct { "debian": {"jq"}, "rhel": {"jq"}, "tencentos": {"jq"}, + "uos": {"jq"}, "arch": {"jq"}, "alpine": {"jq"}, }}, @@ -108,6 +120,7 @@ var capabilities = []struct { "debian": {"socat"}, "rhel": {"socat"}, "tencentos": {"socat"}, + "uos": {"socat"}, "arch": {"socat"}, "alpine": {"socat"}, }}, @@ -115,6 +128,7 @@ var capabilities = []struct { "debian": {"curl"}, "rhel": {"curl"}, "tencentos": {"curl"}, + "uos": {"curl"}, "arch": {"curl"}, "alpine": {"curl"}, }}, @@ -122,6 +136,7 @@ var capabilities = []struct { "debian": {"less"}, "rhel": {"less"}, "tencentos": {"less"}, + "uos": {"less"}, "arch": {"less"}, "alpine": {"less"}, }}, @@ -129,6 +144,7 @@ var capabilities = []struct { "debian": {"iputils-ping"}, "rhel": {"iputils"}, "tencentos": {"iputils"}, + "uos": {"iputils"}, "arch": {"iputils"}, "alpine": {"iputils"}, }}, @@ -138,6 +154,7 @@ var capabilities = []struct { "debian": {"iproute2"}, "rhel": {"iproute"}, "tencentos": {"iproute"}, + "uos": {"iproute"}, "arch": {}, "alpine": {"iproute2"}, }}, @@ -151,6 +168,7 @@ var capabilities = []struct { "debian": {}, "rhel": {"findutils"}, "tencentos": {"findutils"}, + "uos": {"findutils"}, "arch": {}, "alpine": {}, }}, @@ -160,6 +178,7 @@ var capabilities = []struct { "debian": {}, "rhel": {"tar"}, "tencentos": {"tar"}, + "uos": {"tar"}, "arch": {}, "alpine": {}, }}, @@ -169,6 +188,7 @@ var capabilities = []struct { "debian": {}, "rhel": {}, "tencentos": {}, + "uos": {}, "arch": {}, "alpine": {"util-linux-misc"}, }},