Skip to content

Commit c09d1a9

Browse files
committed
add redroid 14.0 to os-image, support android vm
1 parent c64d2e1 commit c09d1a9

11 files changed

Lines changed: 511 additions & 14 deletions

File tree

.github/workflows/build-os-images.yml

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,18 @@ jobs:
8686
done <<< "$(echo "$all_changed" | grep "^os-image/$name/")"
8787
fi
8888
89+
# Detect platform support (default: multi-arch)
90+
platforms="linux/amd64,linux/arm64"
91+
if [[ -f "os-image/$name/platforms" ]]; then
92+
platforms=$(cat "os-image/$name/platforms")
93+
fi
94+
8995
# Find all Dockerfiles for this name
9096
while IFS= read -r dockerfile; do
9197
[[ -z "$dockerfile" ]] && continue
9298
tag=$(echo "$dockerfile" | cut -d'/' -f3)
9399
if [[ -n "$tag" ]]; then
94-
matrix_entries+=("{\"name\":\"$name\",\"tag\":\"$tag\",\"no_cache\":\"$needs_no_cache\"}")
100+
matrix_entries+=("{\"name\":\"$name\",\"tag\":\"$tag\",\"no_cache\":\"$needs_no_cache\",\"platforms\":\"$platforms\"}")
95101
fi
96102
done <<< "$(find "os-image/$name" -name Dockerfile -type f)"
97103
done
@@ -136,21 +142,38 @@ jobs:
136142
images: ghcr.io/${{ github.repository }}/${{ matrix.name }}
137143
tags: |
138144
type=raw,value=${{ matrix.tag }}
139-
type=raw,value=latest
140145
type=sha,prefix=${{ matrix.tag }}-
141146
147+
- name: Discover secret files
148+
id: secrets
149+
run: |
150+
base="os-image/${{ matrix.name }}"
151+
{
152+
echo "files<<EOF"
153+
for f in "$base"/*.sh "$base"/*.rc; do
154+
[ -f "$f" ] || continue
155+
name="$(basename "$f")"
156+
case "$name" in
157+
overlay.sh) id="cocoon_overlay" ;;
158+
network.sh) id="cocoon_network" ;;
159+
*.rc) id="cocoon_$(echo "${name%.rc}" | tr '-' '_')_rc" ;;
160+
*.sh) id="cocoon_$(echo "${name%.sh}" | tr '-' '_')" ;;
161+
esac
162+
echo "${id}=${f}"
163+
done
164+
echo "EOF"
165+
} >> "$GITHUB_OUTPUT"
166+
142167
- name: Build and push
143168
uses: docker/build-push-action@v6
144169
with:
145170
context: os-image/${{ matrix.name }}
146171
file: os-image/${{ matrix.name }}/${{ matrix.tag }}/Dockerfile
147-
platforms: linux/amd64,linux/arm64
172+
platforms: ${{ matrix.platforms }}
148173
push: true
149174
tags: ${{ steps.meta.outputs.tags }}
150175
labels: ${{ steps.meta.outputs.labels }}
151-
secret-files: |
152-
cocoon_overlay=os-image/${{ matrix.name }}/overlay.sh
153-
cocoon_network=os-image/${{ matrix.name }}/network.sh
176+
secret-files: ${{ steps.secrets.outputs.files }}
154177
no-cache: ${{ matrix.no_cache == 'true' }}
155178
cache-from: ${{ matrix.no_cache == 'true' && '' || format('type=gha,scope={0}-{1}', matrix.name, matrix.tag) }}
156179
cache-to: type=gha,mode=max,scope=${{ matrix.name }}-${{ matrix.tag }}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ Download pre-built binaries from [GitHub Releases](https://github.com/projecteru
4343

4444
```bash
4545
# Linux amd64
46-
curl -fsSL -o cocoon https://github.com/projecteru2/cocoon/releases/download/v0.1.7/cocoon_0.1.7_Linux_x86_64.tar.gz
47-
tar -xzf cocoon_0.1.7_Linux_x86_64.tar.gz
46+
curl -fsSL -o cocoon https://github.com/projecteru2/cocoon/releases/download/v0.1.8/cocoon_0.1.8_Linux_x86_64.tar.gz
47+
tar -xzf cocoon_0.1.8_Linux_x86_64.tar.gz
4848
install -m 0755 cocoon /usr/local/bin/
4949

5050
# Or use go install

os-image/README.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
11
# Cocoon OS Images
22

3-
Pre-built OS images are hosted on [GitHub Container Registry](https://github.com/orgs/projecteru2/packages?repo_name=cocoon), supporting both `linux/amd64` and `linux/arm64` architectures.
3+
Pre-built OS images are hosted on [GitHub Container Registry](https://github.com/orgs/projecteru2/packages?repo_name=cocoon).
44

55
## Available Images
66

7+
### Ubuntu
8+
9+
Multi-arch (`linux/amd64`, `linux/arm64`).
10+
711
| Image | Tag | IMAGE_NAME |
812
|-------|-----|------------|
913
| Ubuntu 22.04 (Jammy) | `22.04` | `ghcr.io/projecteru2/cocoon/ubuntu:22.04` |
1014
| Ubuntu 24.04 (Noble) | `24.04` | `ghcr.io/projecteru2/cocoon/ubuntu:24.04` |
11-
| Ubuntu (latest build) | `latest` | `ghcr.io/projecteru2/cocoon/ubuntu:latest` |
15+
| Ubuntu 24.04 + Chrome | `24.04-chrome` | `ghcr.io/projecteru2/cocoon/ubuntu:24.04-chrome` |
16+
| Ubuntu 24.04 + Xfce | `24.04-xface` | `ghcr.io/projecteru2/cocoon/ubuntu:24.04-xface` |
17+
18+
### Android (Redroid)
19+
20+
`linux/amd64` only. Runs Android via [Redroid](https://github.com/remote-android/redroid-doc) directly as PID 1 in the VM — no Ubuntu/systemd layer.
21+
22+
| Image | Tag | IMAGE_NAME |
23+
|-------|-----|------------|
24+
| Android 14 | `14.0` | `ghcr.io/projecteru2/cocoon/android:14.0` |
1225

13-
> All tags are multi-arch manifests. `docker pull` will automatically select the correct architecture for your machine.
26+
Access via `adb connect <vm-ip>:5555` or `scrcpy -s <vm-ip>:5555 --no-audio`.
1427

1528
## Quick Start
1629

30+
### Ubuntu
31+
1732
```bash
18-
IMAGE_NAME="ghcr.io/projecteru2/cocoon/ubuntu:22.04" bash start.sh
33+
IMAGE_NAME="ghcr.io/projecteru2/cocoon/ubuntu:24.04" bash start.sh
1934
```
2035

21-
To use 24.04:
36+
### Android
2237

2338
```bash
24-
IMAGE_NAME="ghcr.io/projecteru2/cocoon/ubuntu:24.04" bash start.sh
39+
IMAGE_NAME="ghcr.io/projecteru2/cocoon/android:14.0" bash start.sh
2540
```
2641

2742
## Prerequisites

os-image/android/14.0/Dockerfile

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# cocoon OCI image: Android (Redroid) — direct boot, no Ubuntu/systemd layer.
2+
#
3+
# Architecture: Host → cocoon VM → Android init (PID 1)
4+
# Boot flow:
5+
# CH --kernel vmlinuz --initramfs initrd.img --disk layer.erofs... --disk cow.ext4
6+
# → initramfs overlay.sh: mount erofs layers + cow → overlayfs rootfs
7+
# → switch_root → /sbin/init (busybox wrapper) → exec /init (Android init, PID 1)
8+
#
9+
# Remote access: adb/scrcpy to <vm-ip>:5555
10+
#
11+
# Layer optimization: All cocoon additions are staged into /output/ in the
12+
# builder, then applied as a single COPY layer to minimize EROFS disk count.
13+
14+
# ---- Stage 1: kernel + initramfs + cocoon extras builder ----
15+
FROM ubuntu:24.04 AS builder
16+
17+
ENV DEBIAN_FRONTEND=noninteractive
18+
19+
RUN --mount=type=secret,id=cocoon_overlay \
20+
--mount=type=secret,id=cocoon_network \
21+
--mount=type=secret,id=cocoon_init_wrapper \
22+
--mount=type=secret,id=cocoon_network_rc \
23+
--mount=type=secret,id=cocoon_omx_fix_rc \
24+
--mount=type=secret,id=cocoon_disable_bt_rc \
25+
# --- Install kernel + initramfs tooling ---
26+
apt-get update && apt-get install -y --no-install-recommends \
27+
linux-image-generic \
28+
initramfs-tools \
29+
busybox-static \
30+
&& \
31+
apt-get install -y --no-install-recommends \
32+
linux-modules-extra-$(ls /lib/modules/ | head -1) \
33+
&& \
34+
# --- cocoon overlay hook (mounts erofs layers + cow → overlayfs root) ---
35+
cp /run/secrets/cocoon_overlay /etc/initramfs-tools/scripts/cocoon-overlay && \
36+
chmod 0755 /etc/initramfs-tools/scripts/cocoon-overlay && \
37+
# --- initramfs modules ---
38+
{ \
39+
echo erofs; echo overlay; echo ext4; \
40+
echo virtio_blk; echo virtio_pci; echo virtio_ring; echo virtio_net; \
41+
echo binder_linux; echo loop; \
42+
} >> /etc/initramfs-tools/modules && \
43+
# netfilter: Android netd requires iptables tables.
44+
KVER="$(ls /lib/modules | sort -V | tail -1)" && \
45+
for m in \
46+
ip_tables iptable_filter iptable_nat iptable_mangle \
47+
ip6_tables ip6table_filter ip6table_nat ip6table_mangle \
48+
nf_conntrack nf_nat nf_conntrack_netlink nfnetlink nfnetlink_log \
49+
xt_connmark xt_NFLOG xt_REJECT xt_state xt_u32 xt_mark xt_MASQUERADE \
50+
; do \
51+
modinfo -k "$KVER" "$m" >/dev/null 2>&1 && echo "$m" >> /etc/initramfs-tools/modules || true; \
52+
done && \
53+
echo 'options binder_linux devices="binder,hwbinder,vndbinder"' \
54+
> /etc/modprobe.d/binder.conf && \
55+
sed -i 's/^COMPRESS=.*/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf && \
56+
update-initramfs -u -k all && \
57+
# --- Stage all outputs into /output/ for single-layer COPY ---
58+
mkdir -p /output/boot /output/lib /output/sbin \
59+
/output/system/bin /output/system/etc/init && \
60+
cp /boot/vmlinuz-* /output/boot/ && \
61+
cp /boot/initrd.img-* /output/boot/ && \
62+
cp -a /lib/modules /output/lib/modules && \
63+
cp /bin/busybox /output/sbin/busybox && \
64+
cp /run/secrets/cocoon_init_wrapper /output/sbin/init && \
65+
chmod 0755 /output/sbin/init && \
66+
cp /run/secrets/cocoon_network /output/system/bin/cocoon-network.sh && \
67+
chmod 0755 /output/system/bin/cocoon-network.sh && \
68+
cp /run/secrets/cocoon_network_rc /output/system/etc/init/cocoon-network.rc && \
69+
cp /run/secrets/cocoon_omx_fix_rc /output/system/etc/init/cocoon-omx-fix.rc && \
70+
cp /run/secrets/cocoon_disable_bt_rc /output/system/etc/init/cocoon-disable-bt.rc && \
71+
rm -rf /var/lib/apt/lists/*
72+
73+
# ---- Stage 2: Android rootfs + single cocoon layer ----
74+
FROM redroid/redroid:14.0.0-latest
75+
76+
# Single layer: kernel, initrd, modules, /sbin/init wrapper, network fix, OMX fix.
77+
COPY --from=builder /output/ /

os-image/android/disable-bt.rc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# /system/etc/init/cocoon-disable-bt.rc
2+
#
3+
# Disable unnecessary components in VM environment.
4+
#
5+
# Bluetooth: no real BT hardware. The sim HAL aborts with "Invalid address",
6+
# and the framework BT service crash-loops trying to connect to the dead HAL.
7+
#
8+
# Play Store: Redroid is AOSP without GApps. com.android.vending crash-loops
9+
# because Google Services Framework (com.google.android.gsf.gservices
10+
# ContentProvider) is missing.
11+
#
12+
# Strategy:
13+
# - early-init: set property to signal BT should be off
14+
# - boot_completed: pm disable packages + clear bluetooth_on flag
15+
# This persists in COW, so only the first boot has crash noise.
16+
17+
on early-init
18+
setprop config.disable_bluetooth true
19+
20+
on property:sys.boot_completed=1
21+
exec - root -- /system/bin/pm disable-user --user 0 com.android.bluetooth
22+
exec - root -- /system/bin/settings put global bluetooth_on 0
23+
exec - root -- /system/bin/pm disable-user --user 0 com.android.vending

os-image/android/init-wrapper.sh

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/sbin/busybox sh
2+
# /sbin/init wrapper: execs Android /init with androidboot.* args.
3+
#
4+
# Why this exists:
5+
# - initramfs-tools switch_root defaults to /sbin/init (or kernel init= param)
6+
# - Android rootfs only has /init (no /sbin/init)
7+
# - androidboot.* params must be in /init's argv (/proc/self/cmdline)
8+
# because redroid's init reads them from there
9+
#
10+
# Key params:
11+
# - use_memfd=1: use memfd when ashmem is unavailable on modern kernels
12+
# - selinux=permissive: required for redroid in VM mode
13+
# - redroid_gpu_mode=guest: force software-render path in VM direct-boot mode
14+
# - use_redroid_c2/use_dmabufheaps: prefer Codec2 path in ashmem-less kernels
15+
# - use_redroid_omx=0: avoid unstable OMX path when gralloc.redroid requires ashmem
16+
#
17+
# Uses busybox (static) as interpreter — no dependency on Android's
18+
# /system/bin/sh or linker64 at this early boot stage.
19+
20+
# Relocate product/system_ext apps to /system/{app,priv-app}/ (system namespace).
21+
#
22+
# In bare VM, everything is on a single overlayfs — Android's libnativeloader
23+
# can't detect partition boundaries (same st_dev), so sandboxed processes get
24+
# "product-clns-N" classloader namespaces with restricted permitted_paths,
25+
# blocking dlopen of native libs (e.g. WebView's libwebviewchromium.so).
26+
# Docker doesn't hit this because its overlay layers create distinct mount points.
27+
#
28+
# Moving apps to /system/app/ puts them in the "system" namespace which has
29+
# full lib access. Runs before Android init scans packages; COW layer absorbs writes.
30+
# Can't do this in Dockerfile RUN — Android rootfs has no standard Linux linker.
31+
BB=/sbin/busybox
32+
for d in /system/product/app/*/; do
33+
[ -d "$d" ] || continue
34+
name=$($BB basename "$d")
35+
[ -e "/system/app/$name" ] || $BB mv "$d" /system/app/
36+
done
37+
for d in /system/product/priv-app/*/; do
38+
[ -d "$d" ] || continue
39+
name=$($BB basename "$d")
40+
[ -e "/system/priv-app/$name" ] || $BB mv "$d" /system/priv-app/
41+
done
42+
for d in /system/system_ext/app/*/; do
43+
[ -d "$d" ] || continue
44+
name=$($BB basename "$d")
45+
[ -e "/system/app/$name" ] || $BB mv "$d" /system/app/
46+
done
47+
for d in /system/system_ext/priv-app/*/; do
48+
[ -d "$d" ] || continue
49+
name=$($BB basename "$d")
50+
[ -e "/system/priv-app/$name" ] || $BB mv "$d" /system/priv-app/
51+
done
52+
53+
# Fix native lib symlinks pointing to product/system_ext paths.
54+
# After moving apps to /system/app/, their lib/ dirs may contain symlinks
55+
# like libjni_latinime.so -> /system/product/lib64/libjni_latinime.so.
56+
# The system namespace can't dlopen from product paths, so replace symlinks
57+
# with copies of the actual files.
58+
for link in $($BB find /system/app /system/priv-app -type l -name '*.so' 2>/dev/null); do
59+
target=$($BB readlink "$link")
60+
case "$target" in
61+
/system/product/*|/system/system_ext/*)
62+
[ -f "$target" ] && $BB cp "$target" "${link}.tmp" && $BB mv "${link}.tmp" "$link"
63+
;;
64+
esac
65+
done
66+
67+
# Disable simulated Bluetooth — no real BT hardware in VM.
68+
# Four layers disabled to fully prevent the crash loop:
69+
# 1. APEX: rename com.android.btservices apex so apexd won't activate it,
70+
# preventing com.android.bluetooth process from ever starting
71+
# 2. HAL binary: prevents "Invalid address" abort from vendor HAL
72+
# 3. VINTF manifest: prevents system_server from discovering BT HAL via HIDL
73+
# 4. config.disable_bluetooth (in cocoon-disable-bt.rc): framework-level disable
74+
for f in $($BB find /system/apex -name '*btservices*' -o -name '*bluetooth*' 2>/dev/null); do
75+
$BB mv "$f" "${f}.disabled" 2>/dev/null
76+
done
77+
$BB mv /vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim \
78+
/vendor/bin/hw/android.hardware.bluetooth@1.1-service.sim.disabled 2>/dev/null
79+
for f in $($BB find /vendor/etc/vintf -name '*bluetooth*' 2>/dev/null); do
80+
$BB mv "$f" "${f}.disabled" 2>/dev/null
81+
done
82+
83+
exec /init \
84+
qemu=1 \
85+
androidboot.hardware=redroid \
86+
androidboot.use_memfd=1 \
87+
androidboot.use_redroid_c2=1 \
88+
androidboot.use_dmabufheaps=1 \
89+
androidboot.use_redroid_omx=0 \
90+
androidboot.selinux=permissive \
91+
androidboot.redroid_gpu_mode=guest \
92+
androidboot.redroid_width=720 \
93+
androidboot.redroid_height=1280 \
94+
androidboot.redroid_dpi=240

os-image/android/network.rc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# /system/etc/init/cocoon-network.rc
2+
#
3+
# Fix VM networking: add default route to netd's policy tables.
4+
# See cocoon-network.sh for details.
5+
#
6+
# Trigger: init.svc.netd=running fires as soon as netd starts,
7+
# much earlier than sys.boot_completed=1. The script has its own
8+
# retry loop to wait for netd's policy tables to become available.
9+
10+
service cocoon-network /system/bin/sh /system/bin/cocoon-network.sh
11+
class late_start
12+
user root
13+
group root net_admin net_raw
14+
oneshot
15+
disabled
16+
17+
on property:init.svc.netd=running
18+
start cocoon-network

0 commit comments

Comments
 (0)