Skip to content
18 changes: 4 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1

FROM scratch AS runner
COPY --from=qemux/qemu:7.37 / /
COPY --from=qemux/qemu:7.39 / /

ARG VERSION_ARG="0.0"
ARG DEBCONF_NOWARNINGS="yes"
Expand All @@ -23,18 +23,11 @@

COPY --chmod=755 ./src /run/

RUN <<'EOF'

Check failure on line 26 in Dockerfile

View workflow job for this annotation

GitHub Actions / Test / shellcheck

SC2016 info: Expressions don't expand in single quotes, use double quotes for that.
set -eu

sed -i \
-e 's|websocket=$WSS_PORT -vga|websocket=$WSS_PORT${LOSSY_OPT:-} -vga|' \
-e 's|"-display vnc=:$port -vga $VGA"|"-display vnc=:$port${LOSSY_OPT:-} -vga $VGA"|' \
-e 's| -vnc :$port,websocket=$WSS_PORT"| -vnc :$port,websocket=$WSS_PORT${LOSSY_OPT:-}"|' \
-e 's| -vnc :$port"| -vnc :$port${LOSSY_OPT:-}"|' \
/run/display.sh

sed -i \
's|if ! enabled "$GPU" || isAmdCpu || \[\[ "$ARCH" != "amd64" \]\]; then|if ! enabled "$GPU" || [[ "$ARCH" != "amd64" ]]; then|' \
's@if ! enabled "$GPU" || isAmdCpu || \[\[ "$ARCH" != "amd64" \]\]; then@if ! enabled "$GPU" || [[ "$ARCH" != "amd64" ]]; then@' \
/run/display.sh

sed -i \
Expand All @@ -45,11 +38,6 @@
's@USB_OPTS="-device $USB"@& \&\& { [[ "${TABLET:-Y}" =~ ^[Yy] ]] \&\& USB_OPTS+=" -device usb-tablet" || USB_OPTS+=" -device usb-mouse"; }@' \
/run/config.sh

[ "$(grep -c 'LOSSY_OPT' /run/display.sh)" -ge 4 ] || {
echo "patch failed: expected 4 LOSSY_OPT sites in display.sh" >&2
exit 1
}

grep -q 'usb-mouse' /run/config.sh || {
echo "patch failed: TABLET conditional not injected into config.sh" >&2
exit 1
Expand All @@ -59,6 +47,8 @@
echo "patch failed: AMD GPU gate not removed from display.sh" >&2
exit 1
}

bash -n /run/display.sh /run/config.sh
EOF

VOLUME /storage
Expand Down
2 changes: 2 additions & 0 deletions kubernetes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ metadata:
name: chromeos
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: chromeos
Expand Down
5 changes: 3 additions & 2 deletions src/entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ set -Eeuo pipefail
: "${SUPPORT:="https://github.com/forkymcforkface/chromeos"}"

: "${AUDIO:="N"}"
: "${LOSSY:="N"}"
: "${SHUTDOWN:="Y"}"
: "${TIMEOUT:="115"}"
: "${TIMEOUT:="105"}"
: "${PLATFORM:="x64"}"
: "${BOOT_MODE:="uefi"}"

Expand Down Expand Up @@ -44,7 +45,7 @@ if ! interactive; then
"${cmd[@]}" ${ARGS:+ $ARGS} &
else
startConsole
setsid -w "${cmd[@]}" ${ARGS:+ $ARGS} </dev/null &
startQemu "${cmd[@]}" ${ARGS:+ $ARGS}
fi

pid=$!
Expand Down
203 changes: 100 additions & 103 deletions src/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fi

# A valid ChromeOS GPT layout signals that Flex is already installed.
isInstalledDisk() {

local table

[ -f "$DATA_IMG" ] && [ -s "$DATA_IMG" ] || return 1
Expand All @@ -50,33 +51,45 @@ isInstalledDisk() {

if isInstalledDisk; then

# Toggle /syslinux/default.cfg between chromeos-vhd (verified) and chromeos-hd (rw rootfs) via mtools — no mount/loop/SYS_ADMIN needed.
# Toggle /syslinux/default.cfg between chromeos-vhd (verified) and
# chromeos-hd (rw rootfs) via mtools — no mount/loop/SYS_ADMIN needed.
if [[ "${DEV_MODE:-}" =~ ^[YyNn] ]]; then
set +e
efi_start=$(sfdisk -d "$DATA_IMG" 2>/dev/null | awk -F'[ =,]+' '/EFI-SYSTEM/ {for(i=1;i<=NF;i++) if($i=="start") print $(i+1)}')
efi_start=$(sfdisk -d "$DATA_IMG" 2>/dev/null |
awk -F'[ =,]+' '/EFI-SYSTEM/ {for(i=1;i<=NF;i++) if($i=="start") print $(i+1)}')

if [ -n "$efi_start" ]; then
efi_off=$((efi_start * 512))
tmp=$(mktemp)

mtype -i "$DATA_IMG@@$efi_off" ::/syslinux/default.cfg > "$tmp" 2>/dev/null

if [ -s "$tmp" ]; then
if [[ "${DEV_MODE:-}" =~ ^[Yy] ]] && grep -q 'chromeos-vhd' "$tmp"; then
sed -i 's|chromeos-vhd|chromeos-hd|g' "$tmp"

mcopy -o -i "$DATA_IMG@@$efi_off" "$tmp" ::/syslinux/default.cfg 2>/dev/null && \
info "DEV_MODE=Y: switched boot default to chromeos-hd.A"

elif [[ "${DEV_MODE:-}" =~ ^[Nn] ]] && grep -q 'chromeos-hd' "$tmp"; then
sed -i 's|chromeos-hd|chromeos-vhd|g' "$tmp"

mcopy -o -i "$DATA_IMG@@$efi_off" "$tmp" ::/syslinux/default.cfg 2>/dev/null && \
info "DEV_MODE=N: switched boot default to chromeos-vhd.A"
fi
fi

rm -f "$tmp"
fi

set -e
fi

info "Booting ChromeOS Flex from data disk."

BOOT="none"
STORAGE="$FLEX_DIR"

return 0
fi

Expand All @@ -86,10 +99,12 @@ fi

if [ -f "/boot.img" ] && [ -s "/boot.img" ]; then
info "Using custom ChromeOS image from /boot.img"

BOOT="/boot.img"
STORAGE="$FLEX_DIR"
BOOT_MODE="uefi"
BOOT_DESC=" ChromeOS Flex (custom)"

return 0
fi

Expand All @@ -99,54 +114,82 @@ fi

if [ -f "$FLEX_DIR/boot.img" ] && [ -s "$FLEX_DIR/boot.img" ]; then
info "Reusing existing installer at $FLEX_DIR/boot.img"

BOOT="$FLEX_DIR/boot.img"
STORAGE="$FLEX_DIR"
BOOT_MODE="uefi"

return 0
fi

downloadFile() {

local url="$1"
local dest="$2"
local message="$3"
local expected="${4:-0}"
local connections="${5:-1}"

downloadToFile \
"$url" \
"$dest" \
"$message" \
"$expected" \
"$connections" \
"Y"
}

downloadImage() {

local url="$1"
local dest="$2"
local base="$3"
local version="$4"
local expected="${5:-0}"
local connections="${CONNECTIONS:-1}"
local msg="Downloading ChromeOS Flex $version"

info "Downloading $base..."

downloadRetry \
"$dest" \
"$connections" \
"5" \
"$base" \
"100000000" \
"$url" \
"$dest" \
"$msg" \
"$expected" || return $?

return 0
}

zipsize="0"

if [ -n "$VERSION_FILTER" ]; then
info "Fetching ChromeOS Flex manifest ($VERSION_LC channel)..."
html "Fetching manifest..."

rc=0
reason=""
log=$(mktemp)
manifest="$FLEX_DIR/manifest.json"

rm -f "$manifest"
rm -f -- "$manifest" "$manifest.aria2"

{
LC_ALL=C wget "$MANIFEST_URL" -O "$manifest" --no-verbose --timeout=30 \
--no-http-keep-alive --output-file="$log"
rc=$?
} || :
if ! downloadToFile \
"$MANIFEST_URL" \
"$manifest" \
"Fetching ChromeOS Flex manifest" \
"0" \
"1" \
"N"; then

if (( rc != 0 )); then
reason=$(sed -n \
-e 's/^wget: //p' \
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$log" | tail -n 1)
fi

rm -f "$log"

if (( rc == 3 )); then
error "Failed to download $MANIFEST_URL because the file could not be written (disk full?)."
exit 60
elif (( rc != 0 )); then
if [ -n "$reason" ]; then
error "Failed to download $MANIFEST_URL: ${reason%.}."
else
error "Failed to download $MANIFEST_URL with exit status $rc."
fi
rm -f -- "$manifest" "$manifest.aria2"
exit 60
fi

if [ ! -s "$manifest" ]; then
error "Manifest is empty!" && exit 60
rm -f -- "$manifest" "$manifest.aria2"
error "Manifest is empty!"
exit 60
fi

url=$(jq -r --arg c "$VERSION_FILTER" '
Expand Down Expand Up @@ -180,115 +223,69 @@ fi
base="$(basename "${url%%\?*}")"
zip_dest="$FLEX_DIR/$base"

progress=()
output=""

# Use Wget's progress bar in a terminal and progress.sh in container logs.
if [ -t 1 ]; then
progress=( --show-progress --progress=bar:noscroll )
else
output="log"
fi

msg="Downloading ChromeOS Flex $version"
html "$msg..."

rc=0
reason=""
log=$(mktemp)

info "Downloading $base..."

rm -f "$zip_dest"
/run/progress.sh "$zip_dest" "${zipsize:-0}" "$msg ([P])..." "$output" &

{
LC_ALL=C wget "$url" -O "$zip_dest" --continue --no-verbose --timeout=30 \
--no-http-keep-alive "${progress[@]}" \
--output-file="$log"
rc=$?
} || :

fKill "progress.sh"

if (( rc != 0 )); then
reason=$(sed -n \
-e 's/^wget: //p' \
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$log" | tail -n 1)
fi

rm -f "$log"

if (( rc == 3 )); then
error "Failed to download $url because the file could not be written (disk full?)."
exit 60
elif (( rc != 0 )); then
if [ -n "$reason" ]; then
error "Failed to download $url: ${reason%.}."
else
error "Failed to download $url with exit status $rc."
fi
exit 60
fi

if [ ! -s "$zip_dest" ]; then
error "Failed to download $url: the downloaded file is empty."
if ! downloadImage \
"$url" \
"$zip_dest" \
"$base" \
"$version" \
"${zipsize:-0}"; then
exit 60
fi

# Catch silent truncation: a valid Flex recovery zip is always >1GB; <100MB means CDN error page or partial download.
if ! actual_size=$(stat -c%s "$zip_dest"); then
error "Failed to determine downloaded file size: $zip_dest"
exit 60
fi

if [ "$actual_size" -lt 100000000 ]; then
error "Downloaded file is suspiciously small ($actual_size bytes)"
exit 60
fi

html "Download finished successfully..."

info "Extracting $base..."
html "Extracting image..."

tmp="$FLEX_DIR/extract"

rm -rf "$tmp"
mkdir -p "$tmp"

extract_size=$(7z l -slt "$zip_dest" 2>/dev/null |
awk -F' = ' '/^Size = [0-9]+$/ && $2 > max { max = $2 } END { print max + 0 }')

/run/progress.sh "$tmp" "${extract_size:-0}" "Extracting image ([P])..." &
/run/progress.sh \
"$tmp" \
"${extract_size:-0}" \
"Extracting image ([P])..." &

rc=0
{ 7z x -y "$zip_dest" -o"$tmp" > /dev/null; rc=$?; } || :

{
7z x -y "$zip_dest" -o"$tmp" > /dev/null
rc=$?
} || :

fKill "progress.sh"

if (( rc != 0 )); then
rm -rf "$tmp"
error "Failed to extract $base" && exit 32

error "Failed to extract $base"
exit 32
fi

img=$(find "$tmp" -type f -iname "*.bin" -print -quit)

if [ ! -s "$img" ]; then
rm -rf "$tmp"
error "Could not find ChromeOS Flex image in archive" && exit 32

error "Could not find ChromeOS Flex image in archive"
exit 32
fi

mv "$img" "$FLEX_DIR/boot.img"

rm -rf "$tmp"
rm -f "$zip_dest"

setOwner "$FLEX_DIR/boot.img" || warn "failed to set owner on installer image"
setOwner "$FLEX_DIR/boot.img" ||
warn "failed to set owner on installer image"

BOOT_MODE="uefi"
BOOT="$FLEX_DIR/boot.img"
STORAGE="$FLEX_DIR"
BOOT_DESC=" ChromeOS Flex $version"

info "ChromeOS Flex installer ready at $BOOT"

return 0
Loading