From 735362e71380d24ebc3a4868def3158776f8b66c Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 6 May 2026 20:30:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20manual=20SKILL=20audit=20?= =?UTF-8?q?=E2=80=94=20small=20wins=20across=2010=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - version: drop unused NAME constant (no callers, no Makefile ldflag) - storage/json: load() now uses utils.ReadJSONFile (~6 lines saved) - network/bridge: New on non-Linux returns errUnsupported instead of re-formatting - progress/{oci,cloudimg}: declare Phase type before its iota const block - hypervisor/utils, hypervisor/cloudhypervisor/helper: same type-before-const fix - cmd/images/handler: same type-before-const fix - images/oci/commit: flatten else-after-return to match the loop above - utils/stream: godoc on NewPipeStreamReader to match siblings --- cmd/images/handler.go | 10 +++++----- hypervisor/cloudhypervisor/helper.go | 6 +++--- hypervisor/utils.go | 6 +++--- images/oci/commit.go | 12 ++++++------ network/bridge/bridge_other.go | 2 +- progress/cloudimg/cloudimg.go | 6 +++--- progress/oci/oci.go | 6 +++--- storage/json/json.go | 15 ++------------- utils/stream.go | 2 ++ version/version.go | 1 - 10 files changed, 28 insertions(+), 38 deletions(-) diff --git a/cmd/images/handler.go b/cmd/images/handler.go index 1fd36f26..c67f0f14 100644 --- a/cmd/images/handler.go +++ b/cmd/images/handler.go @@ -2,6 +2,11 @@ package images import cmdcore "github.com/cocoonstack/cocoon/cmd/core" +// imageType identifies the content type detected from a stream. +type imageType int + +type importSourceKind int + const ( // digestDisplayLen = len("sha256:") + 12 hex digits for compact display. digestDisplayLen = 19 @@ -18,11 +23,6 @@ type Handler struct { cmdcore.BaseHandler } -// imageType identifies the content type detected from a stream. -type imageType int - -type importSourceKind int - type importLocalPlan struct { kind importSourceKind files []string diff --git a/hypervisor/cloudhypervisor/helper.go b/hypervisor/cloudhypervisor/helper.go index 0a8ba36c..005c17c2 100644 --- a/hypervisor/cloudhypervisor/helper.go +++ b/hypervisor/cloudhypervisor/helper.go @@ -16,6 +16,9 @@ import ( "github.com/cocoonstack/cocoon/utils" ) +// chMemoryRestoreMode controls how CH restores guest memory from a snapshot. +type chMemoryRestoreMode string + const ( cmdlineFileName = "cmdline" @@ -26,9 +29,6 @@ const ( var runtimeFiles = []string{hypervisor.APISocketName, "ch.pid", hypervisor.ConsoleSockName, cmdlineFileName, hypervisor.VsockSockName} -// chMemoryRestoreMode controls how CH restores guest memory from a snapshot. -type chMemoryRestoreMode string - type chRestoreConfig struct { SourceURL string `json:"source_url"` MemoryRestoreMode chMemoryRestoreMode `json:"memory_restore_mode,omitempty"` diff --git a/hypervisor/utils.go b/hypervisor/utils.go index 17df610d..33599544 100644 --- a/hypervisor/utils.go +++ b/hypervisor/utils.go @@ -22,6 +22,9 @@ import ( "github.com/cocoonstack/cocoon/utils" ) +// SnapshotFileKind classifies a snapshot file for CloneSnapshotFiles. +type SnapshotFileKind int + const ( // SnapshotFileMemory is a read-only memory/state file (hard link or symlink). SnapshotFileMemory SnapshotFileKind = iota @@ -41,9 +44,6 @@ const ( socketReadyPollInterval = 1 * time.Millisecond ) -// SnapshotFileKind classifies a snapshot file for CloneSnapshotFiles. -type SnapshotFileKind int - func RemoveVMDirs(runDir, logDir string) error { return errors.Join( os.RemoveAll(runDir), diff --git a/images/oci/commit.go b/images/oci/commit.go index 717ba69e..887c03cd 100644 --- a/images/oci/commit.go +++ b/images/oci/commit.go @@ -94,16 +94,16 @@ func commitAndRecord(conf *Config, idx *imageIndex, ref string, manifestDigest i } totalSize += size } - if size, err := validFileSize(conf.KernelPath(kernelLayer.Hex())); err != nil { + size, err := validFileSize(conf.KernelPath(kernelLayer.Hex())) + if err != nil { return fmt.Errorf("kernel missing for %s (concurrent GC?)", kernelLayer) - } else { - totalSize += size } - if size, err := validFileSize(conf.InitrdPath(initrdLayer.Hex())); err != nil { + totalSize += size + size, err = validFileSize(conf.InitrdPath(initrdLayer.Hex())) + if err != nil { return fmt.Errorf("initrd missing for %s (concurrent GC?)", initrdLayer) - } else { - totalSize += size } + totalSize += size idx.Images[ref] = &imageEntry{ Ref: ref, diff --git a/network/bridge/bridge_other.go b/network/bridge/bridge_other.go index 14e4713f..fb4ba4ec 100644 --- a/network/bridge/bridge_other.go +++ b/network/bridge/bridge_other.go @@ -19,7 +19,7 @@ type Bridge struct{} // New returns an error on non-Linux. func New(_ *config.Config, _ string) (*Bridge, error) { - return nil, fmt.Errorf("bridge TAP networking requires Linux (running on %s)", runtime.GOOS) + return nil, errUnsupported } // Type returns the provider identifier. diff --git a/progress/cloudimg/cloudimg.go b/progress/cloudimg/cloudimg.go index c77df43b..102c10b0 100644 --- a/progress/cloudimg/cloudimg.go +++ b/progress/cloudimg/cloudimg.go @@ -1,5 +1,8 @@ package cloudimg +// Phase represents a stage in the cloud image pull lifecycle. +type Phase int + const ( PhaseDownload Phase = iota // HTTP download started. PhaseConvert // Format conversion (qemu-img) started. @@ -7,9 +10,6 @@ const ( PhaseDone // Pull completed successfully. ) -// Phase represents a stage in the cloud image pull lifecycle. -type Phase int - // Event describes a single cloud image pull progress update. type Event struct { Phase Phase diff --git a/progress/oci/oci.go b/progress/oci/oci.go index e1ebbb56..184179bb 100644 --- a/progress/oci/oci.go +++ b/progress/oci/oci.go @@ -1,5 +1,8 @@ package oci +// Phase represents a stage in the OCI pull lifecycle. +type Phase int + const ( PhasePull Phase = iota // Image resolved, layer count known. PhaseLayer // A single layer has been processed. @@ -7,9 +10,6 @@ const ( PhaseDone // Pull completed successfully. ) -// Phase represents a stage in the OCI pull lifecycle. -type Phase int - // Event describes a single OCI pull progress update. type Event struct { Phase Phase diff --git a/storage/json/json.go b/storage/json/json.go index ebbd277a..e24fd4a0 100644 --- a/storage/json/json.go +++ b/storage/json/json.go @@ -2,11 +2,8 @@ package json import ( "context" - "encoding/json" "errors" - "fmt" "io/fs" - "os" "github.com/projecteru2/core/log" @@ -73,16 +70,8 @@ func (s *Store[T]) Unlock(ctx context.Context) error { func (s *Store[T]) load() (*T, error) { var data T - raw, err := os.ReadFile(s.filePath) //nolint:gosec - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - initData(&data) - return &data, nil - } - return nil, fmt.Errorf("read %s: %w", s.filePath, err) - } - if err := json.Unmarshal(raw, &data); err != nil { - return nil, fmt.Errorf("parse %s: %w", s.filePath, err) + if err := utils.ReadJSONFile(s.filePath, &data); err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err } initData(&data) return &data, nil diff --git a/utils/stream.go b/utils/stream.go index 2ef5e85e..8a8e29b2 100644 --- a/utils/stream.go +++ b/utils/stream.go @@ -15,6 +15,8 @@ type PipeStreamReader struct { close func() error } +// NewPipeStreamReader pairs pr with the producer's done channel so Close +// surfaces background errors and runs cleanup exactly once. func NewPipeStreamReader(pr *io.PipeReader, done <-chan error, cleanup func()) *PipeStreamReader { return &PipeStreamReader{ PipeReader: pr, diff --git a/version/version.go b/version/version.go index d41dca4e..76576814 100644 --- a/version/version.go +++ b/version/version.go @@ -6,7 +6,6 @@ import ( ) var ( - NAME = "Cocoon" VERSION = "unknown" REVISION = "HEAD" BUILTAT = "now" From 90bdf84865fbd12618a4949e094cfc1cba1c3e60 Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 6 May 2026 20:35:04 +0800 Subject: [PATCH 2/3] refactor(ch): dedup "ch.pid" via pidFileName const, mirror FC CH had the literal in two places (helper.go runtimeFiles slice and config.go PIDFileName method). FC already centralizes via pidFileName. --- hypervisor/cloudhypervisor/config.go | 2 +- hypervisor/cloudhypervisor/helper.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hypervisor/cloudhypervisor/config.go b/hypervisor/cloudhypervisor/config.go index a19028ea..5df10cae 100644 --- a/hypervisor/cloudhypervisor/config.go +++ b/hypervisor/cloudhypervisor/config.go @@ -19,7 +19,7 @@ func NewConfig(conf *config.Config) *Config { func (c *Config) BinaryName() string { return filepath.Base(c.CHBinary) } -func (c *Config) PIDFileName() string { return "ch.pid" } +func (c *Config) PIDFileName() string { return pidFileName } func (c *Config) COWRawPath(vmID string) string { return filepath.Join(c.VMRunDir(vmID), "cow.raw") diff --git a/hypervisor/cloudhypervisor/helper.go b/hypervisor/cloudhypervisor/helper.go index 005c17c2..59f9643a 100644 --- a/hypervisor/cloudhypervisor/helper.go +++ b/hypervisor/cloudhypervisor/helper.go @@ -20,6 +20,7 @@ import ( type chMemoryRestoreMode string const ( + pidFileName = "ch.pid" cmdlineFileName = "cmdline" // chMemoryRestoreOnDemand uses userfaultfd (UFFD) to lazily page in @@ -27,7 +28,7 @@ const ( chMemoryRestoreOnDemand chMemoryRestoreMode = "OnDemand" ) -var runtimeFiles = []string{hypervisor.APISocketName, "ch.pid", hypervisor.ConsoleSockName, cmdlineFileName, hypervisor.VsockSockName} +var runtimeFiles = []string{hypervisor.APISocketName, pidFileName, hypervisor.ConsoleSockName, cmdlineFileName, hypervisor.VsockSockName} type chRestoreConfig struct { SourceURL string `json:"source_url"` From 1139984f2015a574ed3e176f6a3834b8c63bd098 Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 6 May 2026 20:48:02 +0800 Subject: [PATCH 3/3] os-image: bake cocoon-agent + sshd into every Ubuntu/Android image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ubuntu 22.04 / 24.04 / chrome / xface / picoclaw: install openssh-server, pull cocoon-agent v0.1.0 from upstream releases (multi-arch), drop the systemd unit, enable both ssh + cocoon-agent.service. The download + unit + enable boilerplate is centralized in os-image/ubuntu/install-agent.sh (auto-discovered as build secret cocoon_install_agent), so any future agent-version bump is a one-line change. - Android 14.0 / 15.0: stage cocoon-agent binary into /system/bin/ and install /system/etc/init/cocoon-agent.rc (auto-start on Android init `on boot`). Android stays adb-only for human shell access; no sshd. - Drop LABEL cocoon.ssh.username/password from picoclaw — credentials are now uniformly root:cocoon across all images and documented in os-image/README.md and KNOWN_ISSUES.md. - Add vsock + vmw_vsock_virtio_transport to initramfs modules list across every image so the kernel has the transport ready when cocoon-agent binds AF_VSOCK at boot, regardless of the host's hot-load behavior. KNOWN_ISSUES.md gains two entries: default credentials are dev-only and the Android cocoon-agent service may hit SELinux on stricter redroid builds. README.md / os-image/README.md updated to reflect the unified agent + sshd story. Note: removing the LABEL is a breaking change for any external tooling that scraped `cocoon.ssh.username` / `cocoon.ssh.password` (e.g. glance). Such tooling needs to switch to fixed `root:cocoon` (image default) or read whatever credential store you wire up in your fork. --- KNOWN_ISSUES.md | 18 ++++++++ README.md | 6 +-- os-image/README.md | 9 ++++ os-image/android/14.0/Dockerfile | 10 +++++ os-image/android/15.0/Dockerfile | 10 +++++ os-image/android/agent.rc | 12 +++++ os-image/ubuntu/22.04/Dockerfile | 8 +++- os-image/ubuntu/24.04-chrome/Dockerfile | 5 ++- os-image/ubuntu/24.04-picoclaw/Dockerfile | 12 +++-- os-image/ubuntu/24.04-xface/Dockerfile | 5 ++- os-image/ubuntu/24.04/Dockerfile | 8 +++- os-image/ubuntu/install-agent.sh | 54 +++++++++++++++++++++++ 12 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 os-image/android/agent.rc create mode 100755 os-image/ubuntu/install-agent.sh diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index eaf5eb36..8b98ceac 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -228,3 +228,21 @@ Attaches via `cocoon vm fs attach` and `cocoon vm device attach` are runtime-onl ## virtiofsd is a single-shot daemon Upstream virtiofsd serves exactly one vhost-user client and exits when that client disconnects. Consequence: after `cocoon vm fs detach`, the daemon is gone — a follow-up `cocoon vm fs attach` against the same socket path will hang or time out until a fresh `virtiofsd` instance is launched. The same applies after `cocoon vm stop` (CH closes the socket on shutdown). Scripts that cycle attach/detach should respawn virtiofsd between calls. This is a virtiofsd behavior, not a cocoon limitation. + +## Official OS images ship with default `root:cocoon` and `PermitRootLogin yes` + +Every Ubuntu image under `os-image/` enables `openssh-server` with `PermitRootLogin yes` and the default `root:cocoon` credentials baked in. This is convenient for development and matches the existing OCI-image behavior, but it is **not** safe for production exposure. + +Mitigations for production users: + +- Rotate the root password (`passwd root`) and/or disable password auth (`PasswordAuthentication no`) inside the guest before exposing it. +- Add a non-root sudo user, then flip `PermitRootLogin` back to `no`. +- Or fork the Dockerfile and adjust the `install-agent.sh` invocation to skip the SSH config step. + +Control-plane traffic from cocoon-managed hosts (vk-cocoon, `cocoon vm exec`) goes through cocoon-agent over vsock and never depends on SSH credentials. + +## Android cocoon-agent service may be blocked by SELinux + +`os-image/android/{14.0,15.0}` install the cocoon-agent binary at `/system/bin/cocoon-agent` and register it via `/system/etc/init/cocoon-agent.rc`. Android's SELinux policies don't ship with a domain for cocoon-agent, so the service may run in `init`'s domain or be denied outright depending on the redroid build. + +If `cocoon vm exec` against an Android VM returns `dial agent: ...`, check `logcat | grep -i avc` inside the guest. The fix is build-time — adjust the Android sepolicy to grant the new binary network/socket permissions — and is out of scope for the Dockerfile. diff --git a/README.md b/README.md index 1019a076..cde12b20 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,7 @@ $ cocoon vm exec -e FOO=bar myvm -- sh -c 'echo $FOO' bar ``` -Requires cocoon-agent to be running inside the guest (already baked into the official `ghcr.io/cocoonstack/cocoon/ubuntu:24.04` image and started via systemd). Windows guests are not yet supported. +Requires cocoon-agent to be running inside the guest. All official `ghcr.io/cocoonstack/cocoon/ubuntu:*` and `ghcr.io/cocoonstack/cocoon/android:*` images now bake the binary and enable it on boot (systemd unit on Ubuntu, init.rc service on Android). Windows guests are not yet supported. ### Logs Flags @@ -418,7 +418,7 @@ Cloudimg VMs receive a NoCloud cidata disk (FAT12 with `CIDATA` volume label) co The cidata disk is **automatically excluded on subsequent boots** — after the first successful start, the VM record is marked as `first_booted` and the cidata disk is no longer attached, preventing cloud-init from re-running. -Note: `--user`/`--password` only apply to **cloudimg** VMs (cloud-init). OCI VM images bake credentials at build time. Host-to-guest control plane operations (kubectl exec, kubectl logs) go through cocoon-agent over vsock, not SSH. Only `os-image/ubuntu/24.04-picoclaw` ships sshd; its credentials are documented via `LABEL cocoon.ssh.username` / `cocoon.ssh.password` in the Dockerfile so glance and other SSH-aware tooling can populate their own credential stores. +Note: `--user`/`--password` only apply to **cloudimg** VMs (cloud-init). OCI VM images bake credentials at build time — every official `os-image/ubuntu/*` image ships `openssh-server` enabled with `PermitRootLogin yes` and the default `root:cocoon` credentials. Host-to-guest control plane operations (kubectl exec, kubectl logs) prefer cocoon-agent over vsock; SSH stays available as the human-on-keyboard path. ## Data Disks @@ -807,7 +807,7 @@ cocoon image pull ghcr.io/cocoonstack/cocoon/ubuntu:24.04 cocoon image pull ghcr.io/cocoonstack/cocoon/ubuntu:22.04 ``` -These images include kernel, initramfs, and a systemd-based rootfs with an overlayfs boot script. +These images include kernel, initramfs, and a systemd-based rootfs with an overlayfs boot script. Every official OS image (Ubuntu + Android) bakes `cocoon-agent` (vsock exec) with auto-start; Ubuntu images additionally enable `sshd` with `PermitRootLogin yes` so `ssh root@` works out of the box (default `root:cocoon`). ## Shell Completion diff --git a/os-image/README.md b/os-image/README.md index 6ae2dab4..f9cd8498 100644 --- a/os-image/README.md +++ b/os-image/README.md @@ -68,6 +68,15 @@ IMAGE_NAME="ghcr.io/cocoonstack/cocoon/ubuntu:24.04" bash start.sh IMAGE_NAME="ghcr.io/cocoonstack/cocoon/android:14.0" bash start.sh ``` +## In-VM Services + +Every official OS image bakes the following on top of its base distro: + +- **cocoon-agent** (vsock exec) — pinned binary from [cocoonstack/cocoon-agent](https://github.com/cocoonstack/cocoon-agent), auto-started on boot. Backs `cocoon vm exec` (kubectl-style stdin/stdout/stderr/exit, no SSH/network dependency). Ubuntu uses a systemd unit; Android uses `/system/etc/init/cocoon-agent.rc`. +- **sshd** *(Ubuntu only)* — `openssh-server` enabled with `PermitRootLogin yes`. Default credentials are `root:cocoon`. SSH covers the human-on-keyboard case while cocoon-agent handles control-plane traffic. + +Default credentials apply to fresh VMs. If you fork an image you should rotate the root password and (if you keep sshd) flip `PermitRootLogin` back to `no` once you have a non-root sudoer. + ## DHCP and VM Cloning All Ubuntu images configure systemd-networkd with `ClientIdentifier=mac` in their DHCP settings. This ensures that when a VM is cloned from a snapshot, each clone uses its unique MAC address as the DHCP client identifier instead of the machine-id-derived DUID. Without this, clones from the same snapshot share an identical DUID and dnsmasq treats them as a single client, causing IP conflicts. diff --git a/os-image/android/14.0/Dockerfile b/os-image/android/14.0/Dockerfile index 655129b0..99a34bb0 100644 --- a/os-image/android/14.0/Dockerfile +++ b/os-image/android/14.0/Dockerfile @@ -16,17 +16,21 @@ FROM ubuntu:24.04 AS builder ENV DEBIAN_FRONTEND=noninteractive +ARG COCOON_AGENT_VERSION=0.1.0 + RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ --mount=type=secret,id=cocoon_init_wrapper \ --mount=type=secret,id=cocoon_network_rc \ --mount=type=secret,id=cocoon_omx_fix_rc \ --mount=type=secret,id=cocoon_disable_bt_rc \ + --mount=type=secret,id=cocoon_agent_rc \ # --- Install kernel + initramfs tooling --- apt-get update && apt-get install -y --no-install-recommends \ linux-image-generic \ initramfs-tools \ busybox-static \ + ca-certificates curl \ && \ apt-get install -y --no-install-recommends \ linux-modules-extra-$(ls /lib/modules/ | head -1) \ @@ -38,6 +42,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ { \ echo erofs; echo overlay; echo ext4; \ echo virtio_blk; echo virtio_pci; echo virtio_ring; echo virtio_net; \ + echo vsock; echo vmw_vsock_virtio_transport; \ echo binder_linux; echo loop; \ } >> /etc/initramfs-tools/modules && \ # netfilter: Android netd requires iptables tables. @@ -68,6 +73,11 @@ RUN --mount=type=secret,id=cocoon_overlay \ cp /run/secrets/cocoon_network_rc /output/system/etc/init/cocoon-network.rc && \ cp /run/secrets/cocoon_omx_fix_rc /output/system/etc/init/cocoon-omx-fix.rc && \ cp /run/secrets/cocoon_disable_bt_rc /output/system/etc/init/cocoon-disable-bt.rc && \ + # --- cocoon-agent (vsock exec) — Android amd64 only --- + cp /run/secrets/cocoon_agent_rc /output/system/etc/init/cocoon-agent.rc && \ + curl -fsSL "https://github.com/cocoonstack/cocoon-agent/releases/download/v${COCOON_AGENT_VERSION}/cocoon-agent_${COCOON_AGENT_VERSION}_Linux_x86_64.tar.gz" \ + | tar -xz -C /output/system/bin/ cocoon-agent && \ + chmod 0755 /output/system/bin/cocoon-agent && \ rm -rf /var/lib/apt/lists/* # ---- Stage 2: Android rootfs + single cocoon layer ---- diff --git a/os-image/android/15.0/Dockerfile b/os-image/android/15.0/Dockerfile index 3cd51ce5..3d6431aa 100644 --- a/os-image/android/15.0/Dockerfile +++ b/os-image/android/15.0/Dockerfile @@ -16,17 +16,21 @@ FROM ubuntu:24.04 AS builder ENV DEBIAN_FRONTEND=noninteractive +ARG COCOON_AGENT_VERSION=0.1.0 + RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ --mount=type=secret,id=cocoon_init_wrapper \ --mount=type=secret,id=cocoon_network_rc \ --mount=type=secret,id=cocoon_omx_fix_rc \ --mount=type=secret,id=cocoon_disable_bt_rc \ + --mount=type=secret,id=cocoon_agent_rc \ # --- Install kernel + initramfs tooling --- apt-get update && apt-get install -y --no-install-recommends \ linux-image-generic \ initramfs-tools \ busybox-static \ + ca-certificates curl \ && \ apt-get install -y --no-install-recommends \ linux-modules-extra-$(ls /lib/modules/ | head -1) \ @@ -38,6 +42,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ { \ echo erofs; echo overlay; echo ext4; \ echo virtio_blk; echo virtio_pci; echo virtio_ring; echo virtio_net; \ + echo vsock; echo vmw_vsock_virtio_transport; \ echo binder_linux; echo loop; \ } >> /etc/initramfs-tools/modules && \ # netfilter: Android netd requires iptables tables. @@ -68,6 +73,11 @@ RUN --mount=type=secret,id=cocoon_overlay \ cp /run/secrets/cocoon_network_rc /output/system/etc/init/cocoon-network.rc && \ cp /run/secrets/cocoon_omx_fix_rc /output/system/etc/init/cocoon-omx-fix.rc && \ cp /run/secrets/cocoon_disable_bt_rc /output/system/etc/init/cocoon-disable-bt.rc && \ + # --- cocoon-agent (vsock exec) — Android amd64 only --- + cp /run/secrets/cocoon_agent_rc /output/system/etc/init/cocoon-agent.rc && \ + curl -fsSL "https://github.com/cocoonstack/cocoon-agent/releases/download/v${COCOON_AGENT_VERSION}/cocoon-agent_${COCOON_AGENT_VERSION}_Linux_x86_64.tar.gz" \ + | tar -xz -C /output/system/bin/ cocoon-agent && \ + chmod 0755 /output/system/bin/cocoon-agent && \ rm -rf /var/lib/apt/lists/* # ---- Stage 2: Android rootfs + single cocoon layer ---- diff --git a/os-image/android/agent.rc b/os-image/android/agent.rc new file mode 100644 index 00000000..57d69446 --- /dev/null +++ b/os-image/android/agent.rc @@ -0,0 +1,12 @@ +# /system/etc/init/cocoon-agent.rc +# +# Cocoon vsock exec agent (host-side `cocoon vm exec` listens on AF_VSOCK). +# Persistent service: init restarts the binary if it exits. + +service cocoon-agent /system/bin/cocoon-agent serve + class core + user root + group root + +on boot + start cocoon-agent diff --git a/os-image/ubuntu/22.04/Dockerfile b/os-image/ubuntu/22.04/Dockerfile index 84edc2be..87788c87 100644 --- a/os-image/ubuntu/22.04/Dockerfile +++ b/os-image/ubuntu/22.04/Dockerfile @@ -1,12 +1,14 @@ # Use the latest Ubuntu 22.04 (Jammy) LTS FROM ubuntu:22.04 +ARG TARGETARCH ENV DEBIAN_FRONTEND=noninteractive # Combined System Setup (Single Layer) # Install packages first so /etc/initramfs-tools/scripts/ exists, then inject the hook. RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ + --mount=type=secret,id=cocoon_install_agent \ apt-get update && apt-get install -y --no-install-recommends \ linux-image-virtual \ initramfs-tools \ @@ -16,13 +18,15 @@ RUN --mount=type=secret,id=cocoon_overlay \ udev \ kmod \ iproute2 \ + openssh-server \ + ca-certificates curl \ && \ cp /run/secrets/cocoon_overlay /etc/initramfs-tools/scripts/cocoon-overlay && \ chmod 0755 /etc/initramfs-tools/scripts/cocoon-overlay && \ cp /run/secrets/cocoon_network /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ chmod 0755 /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ # [Kernel Config] - printf "erofs\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\n" >> /etc/initramfs-tools/modules && \ + printf "erofs\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvsock\nvmw_vsock_virtio_transport\n" >> /etc/initramfs-tools/modules && \ sed -i 's/^COMPRESS=.*/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf && \ # [Networking] IP=off prevents initramfs from running DHCP during boot. # Kernel ip= parameters (when present) override this and still trigger ipconfig. @@ -36,6 +40,8 @@ RUN --mount=type=secret,id=cocoon_overlay \ systemctl enable systemd-networkd systemd-resolved systemd-timesyncd && \ mkdir -p /etc/systemd/network && \ printf "[Match]\nName=e* v*\n[Network]\nDHCP=yes\n\n[DHCPv4]\nClientIdentifier=mac\n" > /etc/systemd/network/20-wired.network && \ + # [Cocoon agent + sshd] vsock exec daemon and SSH access. + sh /run/secrets/cocoon_install_agent && \ # [Final Touches] echo 'root:cocoon' | chpasswd && \ rm -rf /var/lib/apt/lists/* diff --git a/os-image/ubuntu/24.04-chrome/Dockerfile b/os-image/ubuntu/24.04-chrome/Dockerfile index cc0386ac..bbcf724e 100644 --- a/os-image/ubuntu/24.04-chrome/Dockerfile +++ b/os-image/ubuntu/24.04-chrome/Dockerfile @@ -5,6 +5,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ + --mount=type=secret,id=cocoon_install_agent \ apt-get update && apt-get install -y --no-install-recommends \ linux-image-virtual \ initramfs-tools \ @@ -13,6 +14,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ software-properties-common htop ncdu nload net-tools \ openbox \ xserver-xorg-core dbus-x11 xrdp xorgxrdp \ + openssh-server ca-certificates \ && \ ARCH=${TARGETARCH:-$(dpkg --print-architecture)}; \ if [ "$ARCH" = "amd64" ]; then \ @@ -32,7 +34,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ chmod 0755 /etc/initramfs-tools/scripts/cocoon-overlay && \ cp /run/secrets/cocoon_network /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ chmod 0755 /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ - printf "erofs\nlz4\nlz4_compress\nzstd\nzstd_compress\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvirtio_gpu\n" >> /etc/initramfs-tools/modules && \ + printf "erofs\nlz4\nlz4_compress\nzstd\nzstd_compress\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvirtio_gpu\nvsock\nvmw_vsock_virtio_transport\n" >> /etc/initramfs-tools/modules && \ sed -i 's/^COMPRESS=.*/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf && \ sed -i '/^IP=/d' /etc/initramfs-tools/initramfs.conf && \ echo 'IP=off' >> /etc/initramfs-tools/initramfs.conf && \ @@ -49,6 +51,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ sed -i 's/^tcp_nodelay=.*/tcp_nodelay=true/' /etc/xrdp/xrdp.ini && \ adduser xrdp ssl-cert && \ systemctl enable xrdp && \ + sh /run/secrets/cocoon_install_agent && \ echo 'root:cocoon' | chpasswd && \ rm -rf /var/lib/apt/lists/* && \ mkdir -p /var/lib/apt/lists/partial /var/cache/apt/archives/partial diff --git a/os-image/ubuntu/24.04-picoclaw/Dockerfile b/os-image/ubuntu/24.04-picoclaw/Dockerfile index 7c82bcb6..0071d091 100644 --- a/os-image/ubuntu/24.04-picoclaw/Dockerfile +++ b/os-image/ubuntu/24.04-picoclaw/Dockerfile @@ -1,11 +1,11 @@ FROM ubuntu:24.04 -LABEL cocoon.ssh.username="root" cocoon.ssh.password="cocoon" ARG TARGETARCH ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ + --mount=type=secret,id=cocoon_install_agent \ --mount=type=secret,id=picoclaw_service \ --mount=type=secret,id=config_json \ --mount=type=secret,id=picoclaw_init \ @@ -15,7 +15,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ systemd systemd-sysv systemd-timesyncd systemd-resolved \ udev kmod iproute2 iputils-ping curl wget arping tcpdump \ software-properties-common htop ncdu nload net-tools \ - openssh-server \ + openssh-server ca-certificates \ openbox \ xserver-xorg-core dbus-x11 xrdp xorgxrdp \ # Chinese language support @@ -58,7 +58,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ chmod 0755 /etc/initramfs-tools/scripts/cocoon-overlay && \ cp /run/secrets/cocoon_network /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ chmod 0755 /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ - printf "erofs\nlz4\nlz4_compress\nzstd\nzstd_compress\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvirtio_gpu\n" >> /etc/initramfs-tools/modules && \ + printf "erofs\nlz4\nlz4_compress\nzstd\nzstd_compress\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvirtio_gpu\nvsock\nvmw_vsock_virtio_transport\n" >> /etc/initramfs-tools/modules && \ sed -i 's/^COMPRESS=.*/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf && \ sed -i '/^IP=/d' /etc/initramfs-tools/initramfs.conf && \ echo 'IP=off' >> /etc/initramfs-tools/initramfs.conf && \ @@ -77,10 +77,8 @@ RUN --mount=type=secret,id=cocoon_overlay \ sed -i 's/^tcp_nodelay=.*/tcp_nodelay=true/' /etc/xrdp/xrdp.ini && \ adduser xrdp ssl-cert && \ systemctl enable xrdp && \ - # SSH: enable root login - mkdir -p /run/sshd && \ - sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \ - systemctl enable ssh && \ + # SSH + cocoon-agent (vsock exec): unified install script. + sh /run/secrets/cocoon_install_agent && \ # Root password echo 'root:cocoon' | chpasswd && \ # PicoClaw: config + service + init script (NOT enabled until picoclaw-init runs) diff --git a/os-image/ubuntu/24.04-xface/Dockerfile b/os-image/ubuntu/24.04-xface/Dockerfile index 57df4766..1014a719 100644 --- a/os-image/ubuntu/24.04-xface/Dockerfile +++ b/os-image/ubuntu/24.04-xface/Dockerfile @@ -5,6 +5,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ + --mount=type=secret,id=cocoon_install_agent \ apt-get update && apt-get install -y --no-install-recommends \ linux-image-virtual \ initramfs-tools \ @@ -13,6 +14,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ software-properties-common htop ncdu nload net-tools \ xfce4 xfce4-terminal xfce4-screenshooter \ xserver-xorg-core dbus-x11 xrdp xorgxrdp \ + openssh-server ca-certificates \ && \ ARCH=${TARGETARCH:-$(dpkg --print-architecture)}; \ if [ "$ARCH" = "amd64" ]; then \ @@ -34,7 +36,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ chmod 0755 /etc/initramfs-tools/scripts/cocoon-overlay && \ cp /run/secrets/cocoon_network /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ chmod 0755 /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ - printf "erofs\nlz4\nlz4_compress\nzstd\nzstd_compress\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvirtio_gpu\n" >> /etc/initramfs-tools/modules && \ + printf "erofs\nlz4\nlz4_compress\nzstd\nzstd_compress\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvirtio_gpu\nvsock\nvmw_vsock_virtio_transport\n" >> /etc/initramfs-tools/modules && \ sed -i 's/^COMPRESS=.*/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf && \ sed -i '/^IP=/d' /etc/initramfs-tools/initramfs.conf && \ echo 'IP=off' >> /etc/initramfs-tools/initramfs.conf && \ @@ -53,6 +55,7 @@ RUN --mount=type=secret,id=cocoon_overlay \ printf '\n\n \n \n \n\n' > /root/.config/xfce4/xfconf/xfce-perchannel-xml/xfwm4.xml && \ adduser xrdp ssl-cert && \ systemctl enable xrdp && \ + sh /run/secrets/cocoon_install_agent && \ echo 'root:cocoon' | chpasswd && \ rm -rf /var/lib/apt/lists/* && \ mkdir -p /var/lib/apt/lists/partial /var/cache/apt/archives/partial diff --git a/os-image/ubuntu/24.04/Dockerfile b/os-image/ubuntu/24.04/Dockerfile index c812458e..681ff8bf 100644 --- a/os-image/ubuntu/24.04/Dockerfile +++ b/os-image/ubuntu/24.04/Dockerfile @@ -1,12 +1,14 @@ # Use the latest Ubuntu 24.04 (Noble) LTS FROM ubuntu:24.04 +ARG TARGETARCH ENV DEBIAN_FRONTEND=noninteractive # Combined System Setup & Optimization (Single Layer) # Install packages first so /etc/initramfs-tools/scripts/ exists, then inject the hook. RUN --mount=type=secret,id=cocoon_overlay \ --mount=type=secret,id=cocoon_network \ + --mount=type=secret,id=cocoon_install_agent \ apt-get update && apt-get install -y --no-install-recommends \ linux-image-virtual \ initramfs-tools \ @@ -17,13 +19,15 @@ RUN --mount=type=secret,id=cocoon_overlay \ udev \ kmod \ iproute2 iputils-ping curl wget arping tcpdump \ + openssh-server \ + ca-certificates \ && \ cp /run/secrets/cocoon_overlay /etc/initramfs-tools/scripts/cocoon-overlay && \ chmod 0755 /etc/initramfs-tools/scripts/cocoon-overlay && \ cp /run/secrets/cocoon_network /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ chmod 0755 /etc/initramfs-tools/scripts/init-bottom/cocoon-network && \ # [Kernel Setup] Force critical modules and set gzip compression - printf "erofs\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\n" >> /etc/initramfs-tools/modules && \ + printf "erofs\noverlay\next4\nvirtio_blk\nvirtio_pci\nvirtio_ring\nvirtio_net\nvsock\nvmw_vsock_virtio_transport\n" >> /etc/initramfs-tools/modules && \ sed -i 's/^COMPRESS=.*/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf && \ # [Networking] IP=off prevents initramfs from running DHCP during boot. # Kernel ip= parameters (when present) override this and still trigger ipconfig. @@ -40,6 +44,8 @@ RUN --mount=type=secret,id=cocoon_overlay \ systemctl enable systemd-networkd systemd-resolved systemd-timesyncd && \ mkdir -p /etc/systemd/network && \ printf "[Match]\nName=e* v*\n[Network]\nDHCP=yes\n\n[DHCPv4]\nClientIdentifier=mac\n" > /etc/systemd/network/20-wired.network && \ + # [Cocoon agent + sshd] vsock exec daemon and SSH access. + sh /run/secrets/cocoon_install_agent && \ # [Access] Set root password echo 'root:cocoon' | chpasswd && \ # [Cleanup] Purge APT cache to minimize EROFS size diff --git a/os-image/ubuntu/install-agent.sh b/os-image/ubuntu/install-agent.sh new file mode 100755 index 00000000..91bb1396 --- /dev/null +++ b/os-image/ubuntu/install-agent.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# Install cocoon-agent (vsock exec) and sshd into a Debian/Ubuntu image. +# Caller is expected to have already installed `openssh-server` via apt +# in the same RUN, and to have curl available. +# +# Idempotent: re-running the script overwrites the binary and unit file, +# `systemctl enable` is a no-op when the symlinks are already in place. +set -eu + +AGENT_VERSION="${COCOON_AGENT_VERSION:-0.1.0}" +ARCH="${TARGETARCH:-$(dpkg --print-architecture)}" +case "$ARCH" in + amd64) AGENT_ARCH="x86_64" ;; + arm64) AGENT_ARCH="arm64" ;; + *) echo "install-agent: unsupported arch '$ARCH'" >&2; exit 1 ;; +esac + +# 1. sshd: permit root login (cocoon images use root:cocoon by default). +mkdir -p /run/sshd +sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config +systemctl enable ssh + +# 2. cocoon-agent binary: pinned-version tarball from upstream releases. +TARBALL="cocoon-agent_${AGENT_VERSION}_Linux_${AGENT_ARCH}.tar.gz" +URL="https://github.com/cocoonstack/cocoon-agent/releases/download/v${AGENT_VERSION}/${TARBALL}" +curl -fsSL "$URL" | tar -xz -C /usr/local/bin/ cocoon-agent +chmod 0755 /usr/local/bin/cocoon-agent + +# 3. systemd unit. Mirrors upstream packaging/cocoon-agent.service so the +# in-VM service stays in sync with what cocoon-agent is tested against. +cat > /etc/systemd/system/cocoon-agent.service <<'EOF' +[Unit] +Description=Cocoon agent (vsock command exec) +Documentation=https://github.com/cocoonstack/cocoon-agent + +[Service] +Type=simple +User=root +Group=root +# Best-effort load — most kernels build the transport in or auto-load on +# virtio-vsock device probe; the leading dash keeps the unit alive on +# minimal kernels (e.g. ubuntu linux-image-virtual). +ExecStartPre=-/sbin/modprobe vhost_vsock +ExecStart=/usr/local/bin/cocoon-agent serve +Environment=AGENT_LOG_LEVEL=info +Restart=always +RestartSec=2s +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target +EOF + +systemctl enable cocoon-agent.service