diff --git a/.gitignore b/.gitignore index 6bcbf99..cba76de 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ qcom_xbl/ .vscode edk2_tici/ +*.tar.xz diff --git a/Dockerfile.void b/Dockerfile.void new file mode 100644 index 0000000..4d55b49 --- /dev/null +++ b/Dockerfile.void @@ -0,0 +1,167 @@ +# check=error=true +# SPIKE: AGNOS on Void Linux (glibc, runit, eudev). See userspace/void/. + +# ################## # +# ### Package ##### # +# ### Compilation ## # +# ################## # +# The adbd and graphics stages are the same Ubuntu stages as Dockerfile.agnos. +FROM ubuntu:24.04 AS agnos-compiler + +RUN apt-get update && apt-get install -yq --no-install-recommends build-essential + +FROM agnos-compiler AS agnos-compiler-adbd +RUN apt-get update && apt-get install -yq --no-install-recommends \ + ca-certificates curl clang android-libbase-dev android-libcutils-dev android-liblog-dev \ + android-libboringssl-dev libbrotli-dev libcap-dev liblz4-dev libprotobuf-dev protobuf-compiler \ + libsystemd-dev libzstd-dev +COPY ./userspace/build_adbd.sh ./userspace/adbd-linux.patch /tmp/agnos/ +RUN /tmp/agnos/build_adbd.sh +# Void has no Android libs, protobuf 35 instead of 3.21 and no libsystemd: +# bundle every non-glibc/libstdc++ dependency of adbd from Ubuntu. +RUN mkdir -p /tmp/agnos/adbd-libs && \ + ldd /tmp/agnos/adbd | awk '$2 == "=>" && $3 ~ /^\// { print $3 }' | \ + grep -vE '/(libc|libm|libdl|libpthread|librt|ld-linux-aarch64|libstdc\+\+|libgcc_s)\.so' | \ + xargs -I{} cp -L {} /tmp/agnos/adbd-libs/ && \ + ls -la /tmp/agnos/adbd-libs + +FROM agnos-compiler AS agnos-compiler-graphics +RUN apt-get update && apt-get install -yq --no-install-recommends patchelf +COPY ./userspace/graphics/ /tmp/agnos/graphics-src/ +RUN mkdir -p /tmp/agnos/graphics && \ + cp /tmp/agnos/graphics-src/libgbm.so /tmp/agnos/graphics/ && \ + cp /tmp/agnos/graphics-src/libeglSubDriverWayland.so.patched /tmp/agnos/graphics/libeglSubDriverWayland.so && \ + cd /tmp/agnos/graphics && \ + gcc -shared -fPIC -Os -s -Wall -Wextra -Werror -Wl,-z,defs -Wl,-z,relro,-z,now \ + -Wl,-soname,libagnos-wayland-stubs.so -o libagnos-wayland-stubs.so /tmp/agnos/graphics-src/stubs.c && \ + gcc -shared -fPIC -Os -s -Wall -Wextra -Werror -Wl,-z,defs -Wl,-z,relro,-z,now \ + -Wl,-soname,libCB.so -o libCB.so /tmp/agnos/graphics-src/opencl_stubs.c && \ + nm -D --defined-only libagnos-wayland-stubs.so | \ + awk '$3 ~ /^agnos_/ { name = $3; sub(/^agnos_/, "", name); print name, $3 }' > /tmp/agnos/graphics-symbols.map && \ + for lib in libgbm.so libeglSubDriverWayland.so; do \ + patchelf --rename-dynamic-symbols /tmp/agnos/graphics-symbols.map \ + --remove-needed libwayland-client.so.0 --remove-needed libwayland-server.so.0 \ + --remove-needed libwayland-egl.so.1 --remove-needed libffi.so.6 \ + --add-needed libagnos-wayland-stubs.so "$lib" || exit 1; \ + done + +# ################## # +# ###### Base ###### # +# ################## # +FROM scratch AS void-base + +ARG VOID_ROOTFS +ADD ${VOID_ROOTFS} / + +RUN mkdir -p /tmp/agnos +COPY ./userspace/void/base_setup.sh /tmp/agnos/void/ +RUN /tmp/agnos/void/base_setup.sh + +COPY ./userspace/void/python_setup.sh /tmp/agnos/void/ +RUN /tmp/agnos/void/python_setup.sh + +# ################### # +# ###### AGNOS ###### # +# ################### # +FROM void-base AS agnos + +ARG USERNAME=comma + +COPY --from=agnos-compiler-adbd /tmp/agnos/adbd /usr/comma/adbd +COPY --from=agnos-compiler-adbd /tmp/agnos/adbd-libs/ /usr/lib/aarch64-linux-gnu/android/ +COPY --from=agnos-compiler-adbd /tmp/adbd-build/packages/modules/adb/NOTICE /usr/share/doc/adbd/NOTICE +COPY --from=agnos-compiler-graphics /tmp/agnos/graphics/ /usr/lib/aarch64-linux-gnu/ + +ARG XDG_DATA_HOME="/usr/local" + +# Install openpilot python packages +COPY ./userspace/uv /tmp/agnos/uv +RUN source $XDG_DATA_HOME/venv/bin/activate && \ + cd /tmp/agnos/uv && \ + MAKEFLAGS="-j$(nproc)" UV_NO_CACHE=1 UV_PROJECT_ENVIRONMENT=$XDG_DATA_HOME/venv uv sync --frozen --inexact --compile-bytecode \ + --no-install-package msgq \ + --no-install-package opendbc \ + --no-install-package pandacan \ + --no-install-package rednose \ + --no-install-package teleoprtc \ + --no-install-package tinygrad + +# Root filesystem overlay. Void merges /usr/sbin into /usr/bin and links +# /usr/lib64 to /usr/lib: keep those directory symlinks, and skip the overlay's +# usr/lib64/ld-linux-aarch64.so.1 -> /lib/... link, which would become a self-loop. +RUN --mount=type=bind,source=userspace/root,target=/tmp/agnos/root \ + set -o pipefail && \ + tar -C /tmp/agnos/root --owner=0 --group=0 --mode=go-w --exclude=./usr/lib64 -cf - . | \ + tar -C / --no-overwrite-dir --keep-directory-symlink -xf - && \ + test -x /usr/bin/rmt_storage && test -f /usr/lib/ld-linux-aarch64.so.1 && test ! -L /usr/lib/ld-linux-aarch64.so.1 +COPY ./userspace/root/home/$USERNAME/.config/ /root/.config +RUN chown -R $USERNAME: /home/$USERNAME/.config + +COPY ./output/linux-headers/include/ /usr/include/ + +COPY ./userspace/irsc_util.c /tmp/agnos/ +RUN gcc -O2 -Wall -Wextra -Werror -o /usr/bin/irsc_util /tmp/agnos/irsc_util.c && rm /tmp/agnos/irsc_util.c + +COPY ./userspace/power_burn/power_burn_max.c /tmp/agnos/ +RUN gcc -O2 -o /home/$USERNAME/power_burn_max /tmp/agnos/power_burn_max.c \ + -lpthread && rm /tmp/agnos/power_burn_max.c \ + && chown $USERNAME: /home/$USERNAME/power_burn_max + +# color prompt (install_extras.sh) +RUN sed -i 's/#force_color_prompt=yes/force_color_prompt=yes/g' /home/$USERNAME/.bashrc || true + +# Prefer ipv4 over ipv6 +RUN echo "precedence ::ffff:0:0/96 100" >> /etc/gai.conf + +# trim.sh, minus the Ubuntu library paths (handled in finalize.sh) +RUN site=/usr/local/venv/lib/python3.12/site-packages && \ + find "$site"/{tinymesa,capnp,zstandard,kiwisolver,fontTools} -type f -name '*.so' \ + -exec llvm-strip --strip-debug {} + && \ + find "$site"/{sympy,numpy,matplotlib,mpmath,setuptools} -type d \( -name tests -o -name test \) \ + -prune -exec rm -rf {} + && \ + rm -f /usr/bin/*lto-dump* /usr/lib/llvm/*/bin/llvm-exegesis /usr/local/bin/llvm-exegesis + +# Services, Void config fixups, read-only rootfs. Keep this last (runs ldconfig +# before pointing the GL SONAME links at Adreno). +COPY ./userspace/void/ /tmp/agnos/void/ +RUN /tmp/agnos/void/finalize.sh && rm -rf /tmp/agnos + +COPY VERSION /VERSION + +RUN rm -rf /usr/share/icons/* /home/$USERNAME/.cache /root/.cache /usr/default/home/$USERNAME/.cache /usr/default/var/cache/xbps + +# ################## # +# ##### Image ###### # +# ################## # +# Same as Dockerfile.agnos, except resolv.conf and ping. +FROM --platform=$BUILDPLATFORM ubuntu:20.04 AS agnos-image-tools +RUN apt-get update && \ + apt-get install -yq --no-install-recommends \ + android-sdk-libsparse-utils \ + e2fsprogs \ + libcap2-bin \ + zstd \ + && rm -rf /var/lib/apt/lists/* + +FROM agnos-image-tools AS agnos-image-build +ARG ROOTFS_IMAGE_SIZE=4500M +ARG GIT_HASH +ARG BUILD_DATETIME +ARG ZSTD +# No systemd-resolved: NetworkManager writes resolv.conf itself (dns=default). +# bin/ping is a symlink on Void, so set the capability on the real binary. +RUN --mount=type=bind,from=agnos,target=/rootfs,rw \ + cd /rootfs && \ + ln -sf /proc/sys/kernel/hostname etc/hostname && \ + printf "127.0.0.1 localhost.localdomain localhost\n127.0.0.1 comma\n" > etc/hosts && \ + ln -sf /run/NetworkManager/resolv.conf etc/resolv.conf && \ + setcap cap_net_raw+ep usr/bin/iputils-ping && \ + printf "%s\n%s\n" "$GIT_HASH" "$BUILD_DATETIME" > BUILD && \ + mkdir /out && \ + mkfs.ext4 -q -d /rootfs /tmp/system.img $ROOTFS_IMAGE_SIZE && \ + img2simg /tmp/system.img /out/system.img && \ + rm /tmp/system.img && \ + if [ -n "$ZSTD" ]; then zstd -q -T0 -9 --rm /out/system.img; fi + +FROM scratch AS agnos-image +COPY --from=agnos-image-build /out/ / diff --git a/build_system.sh b/build_system.sh index 3c34473..8433f96 100755 --- a/build_system.sh +++ b/build_system.sh @@ -5,6 +5,17 @@ UBUNTU_BASE_URL="https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/" UBUNTU_FILE="ubuntu-base-24.04.3-base-arm64.tar.gz" UBUNTU_FILE_CHECKSUM="7b2dced6dd56ad5e4a813fa25c8de307b655fdabc6ea9213175a92c48dabb048" +# SPIKE: VOID=1 builds Dockerfile.void (Void Linux, runit) instead +VOID_BASE_URL="https://repo-default.voidlinux.org/live/20250202" +VOID_FILE="void-aarch64-ROOTFS-20250202.tar.xz" +VOID_FILE_CHECKSUM="01a30f17ae06d4d5b322cd579ca971bc479e02cc284ec1e5a4255bea6bac3ce6" +DOCKERFILE=Dockerfile.agnos +BASE_ARG="UBUNTU_BASE_IMAGE=$UBUNTU_FILE" +if [ -n "$VOID" ]; then + DOCKERFILE=Dockerfile.void + BASE_ARG="VOID_ROOTFS=$VOID_FILE" +fi + # Make sure we're in the correct spot DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" cd $DIR @@ -32,6 +43,17 @@ if [ "$(shasum -a 256 "$UBUNTU_FILE" | awk '{print $1}')" != "$UBUNTU_FILE_CHECK exit 1 fi +if [ -n "$VOID" ]; then + if [ ! -f $VOID_FILE ]; then + echo "Downloading Void rootfs: $VOID_FILE" + curl -C - -o $VOID_FILE $VOID_BASE_URL/$VOID_FILE --silent --remote-time --fail + fi + if [ "$(shasum -a 256 "$VOID_FILE" | awk '{print $1}')" != "$VOID_FILE_CHECKSUM" ]; then + echo "Checksum mismatch: $VOID_FILE" + exit 1 + fi +fi + # Setup qemu multiarch, not needed for remote arm64 builds if [ "$(uname -m)" = "x86_64" ] && [ -z "$NS" ]; then echo "Registering emulator" @@ -40,7 +62,7 @@ fi # Check agnos-builder Dockerfile export DOCKER_BUILDKIT=1 -docker buildx build -f Dockerfile.agnos --check $DIR +docker buildx build -f $DOCKERFILE --check $DIR # Build the system image in docker, only the sparse image is exported echo "Building system image" @@ -54,8 +76,8 @@ if [ ! -z "$NS" ]; then fi GIT_HASH=${GIT_HASH:-$(git --git-dir=$DIR/.git rev-parse HEAD)} rm -rf $BUILD_DIR/image -$BUILD -f Dockerfile.agnos $DIR --platform=linux/arm64 \ - --build-arg UBUNTU_BASE_IMAGE=$UBUNTU_FILE \ +$BUILD -f $DOCKERFILE $DIR --platform=linux/arm64 \ + --build-arg $BASE_ARG \ --build-arg GIT_HASH=$GIT_HASH \ --build-arg BUILD_DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') if [ -f $BUILD_DIR/image/system.img.zst ]; then diff --git a/userspace/void/base_setup.sh b/userspace/void/base_setup.sh new file mode 100755 index 0000000..bd78b5b --- /dev/null +++ b/userspace/void/base_setup.sh @@ -0,0 +1,116 @@ +#!/bin/bash -e +# Void Linux equivalent of base_setup.sh + openpilot_dependencies.sh + +# install_extras.sh + the apt installs in Dockerfile.agnos. +# Package mapping (Ubuntu -> Void) is annotated inline; "GAP" = no equivalent. + +USERNAME=comma +PASSWD=comma + +touch /TICI /AGNOS + +xbps-install -Syu xbps +xbps-install -yu + +PKGS=( + # --- base_setup.sh --- + shadow # adduser + alsa-utils + # GAP apport-retrace: no apport on Void + bc + base-devel # build-essential + dnsmasq # dnsmasq-base + gdb + git + iptables # iptables-persistent: GAP, Void's iptables ships a runit service (/etc/sv/iptables) that loads /etc/iptables/iptables.rules + iputils + jq + kmod + less + glibc-devel # libc6-dev + libglvnd-devel # libegl-dev libgles-dev + libglvnd # libegl1 libgles1 libgles2 + # GAP libnss-myhostname: no nss-myhostname on Void (needs systemd) + llvm21 # llvm-20: GAP, Void has 18/19/21; tinymesa wants LLVM 20 + glibc-locales # locales + logrotate + lsof + NetworkManager # network-manager + openssh # openssh-client openssh-server + openssl + ppp + psmisc + rsync + rsyslog + strace + runit-void # systemd + # GAP systemd-resolved: NetworkManager dns=default + openresolv + chrony # systemd-timesyncd + tmux + eudev # udev + usbutils + vim + wpa_supplicant # wpasupplicant + ca-certificates curl sudo wget + # --- openpilot_dependencies.sh --- + clang21 # clang-20 (see llvm21) + dbus-devel # libdbus-1-dev + libdrm-devel # libdrm-dev + libgbm-devel # libgbm-dev + glib # libglib2.0-0t64 + portaudio # libportaudio2 + pkg-config + # --- Dockerfile.agnos final stage --- + python3 # python3 (3.14 on Void; 3.12 comes from uv, see python_setup.sh) + python3-devel # python3-dev + glib-devel # libglib2.0-dev + libgudev # libgudev-1.0-0 + libmm-glib # libmm-glib0 + polkit # polkitd libpolkit-gobject-1-0 + # GAP libsystemd0: adbd gets a bundled Ubuntu copy + # --- install_extras.sh --- + bash-completion btop hyperfine iperf iperf3 ripgrep ncdu socat stress-ng tree wavemon + avahi avahi-utils + android-tools # adb + # --- Void-only: things Ubuntu pulled in implicitly --- + bash dbus util-linux iproute2 procps-ng tzdata e2fsprogs xz file + uv # Ubuntu used astral's installer; Void packages uv + patchelf # adbd RPATH fixup + libcap-progs # setcap for ping +) +xbps-install -y "${PKGS[@]}" + +# Create the privileged user and hardware access groups. +# Void has no "sudo" group (it uses wheel); create it so comma-polkit.rules keeps working. +for g in sudo gpio gpu netdev systemd-journal syslog; do + getent group "$g" >/dev/null || groupadd -r "$g" +done +useradd -G sudo,wheel -m -s /bin/bash "$USERNAME" +echo "$USERNAME:$PASSWD" | chpasswd +usermod -aG root,video,gpio,adm,gpu,audio,disk,dialout,systemd-journal,netdev,input,plugdev "$USERNAME" +echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Kernel and scheduling settings. +echo "net.ipv4.conf.all.rp_filter = 2" >> /etc/sysctl.conf +echo "vm.dirty_expire_centisecs = 200" >> /etc/sysctl.conf +echo "$USERNAME - rtprio 100" >> /etc/security/limits.conf +echo "$USERNAME - nice -10" >> /etc/security/limits.conf + +# locales: Void uses /etc/default/libc-locales + xbps-reconfigure +sed -i 's/^#en_US.UTF-8/en_US.UTF-8/' /etc/default/libc-locales +xbps-reconfigure -f glibc-locales +echo "LANG=en_US.UTF-8" > /etc/locale.conf + +install -d -o "$USERNAME" -g "$USERNAME" /data /persist +install -d /config +ln -sf /bin/bash /bin/sh + +# Versioned LLVM packages don't provide the unversioned commands (same as Ubuntu). +for tool in /usr/lib/llvm/21/bin/*; do + ln -sf "$tool" "/usr/local/bin/$(basename "$tool")" +done + +# The Qualcomm blobs live in the Debian multiarch dir. +echo /usr/lib/aarch64-linux-gnu > /etc/ld.so.conf.d/00-aarch64-linux-gnu.conf + +xbps-remove -Oy || true +rm -rf /var/cache/xbps/* diff --git a/userspace/void/bin/systemctl b/userspace/void/bin/systemctl new file mode 100755 index 0000000..95dec7c --- /dev/null +++ b/userspace/void/bin/systemctl @@ -0,0 +1,33 @@ +#!/bin/sh +# AGNOS-on-Void spike: minimal systemctl -> runit sv shim. +# Covers what AGNOS scripts and openpilot call: start/stop/restart/is-active. +# runsv's supervise dirs are root-only, so non-root callers go through sudo +# (comma has NOPASSWD sudo). On Ubuntu this was polkit/D-Bus. +[ "$(id -u)" = 0 ] || exec sudo -n "$0" "$@" +quiet=0; args="" +for a in "$@"; do + case "$a" in + -q|--quiet) quiet=1 ;; + --no-block|--now|--user) ;; + *) args="$args $a" ;; + esac +done +set -- $args +cmd=$1; shift +map() { n=${1%.service}; case "$n" in ssh) n=sshd ;; avahi-daemon) n=avahi-daemon ;; esac; echo "$n"; } +rc=0 +for u in "$@"; do + s=$(map "$u") + case "$cmd" in + start|stop|restart|reload|status) out=$(sv "$cmd" "$s" 2>&1) || rc=$? ;; + try-restart) out=$(sv try-restart "$s" 2>&1) || rc=$? ;; + kill) out=$(sv kill "$s" 2>&1) || rc=$? ;; + is-active) if sv status "$s" 2>/dev/null | grep -q '^run:'; then out=active; else out=inactive; rc=3; fi ;; + is-enabled) if [ -e "/etc/runit/runsvdir/default/$s" ] && [ ! -e "/etc/sv/$s/down" ]; then out=enabled; else out=disabled; rc=1; fi ;; + enable|disable|mask|unmask|daemon-reload) out="systemctl $cmd: no-op on runit"; ;; + *) echo "systemctl shim: unsupported command '$cmd'" >&2; exit 1 ;; + esac + [ $quiet = 1 ] || [ -z "$out" ] || echo "$out" +done +[ "$cmd" = daemon-reload ] && exit 0 +exit $rc diff --git a/userspace/void/bin/systemd-cat b/userspace/void/bin/systemd-cat new file mode 100755 index 0000000..a736c89 --- /dev/null +++ b/userspace/void/bin/systemd-cat @@ -0,0 +1,14 @@ +#!/bin/sh +# systemd-cat -t TAG cmd... -> pipe through logger (syslog) +tag=systemd-cat +while [ $# -gt 0 ]; do + case "$1" in + -t) tag=$2; shift 2 ;; + -t*) tag=${1#-t}; shift ;; + -p) shift 2 ;; + --) shift; break ;; + -*) shift ;; + *) break ;; + esac +done +"$@" 2>&1 | logger -t "$tag" diff --git a/userspace/void/bin/systemd-tmpfiles b/userspace/void/bin/systemd-tmpfiles new file mode 100755 index 0000000..254911a --- /dev/null +++ b/userspace/void/bin/systemd-tmpfiles @@ -0,0 +1,18 @@ +#!/bin/bash +# Tiny systemd-tmpfiles subset (d/q/f lines, --create) for the Void spike. +shift_create=0 +files=() +for a in "$@"; do case "$a" in --create) ;; -*) ;; *) files+=("$a");; esac; done +for f in "${files[@]}"; do + while read -r type path mode user group _; do + case "$type" in ''|\#*) continue ;; esac + [ "$mode" = - ] && mode=; [ "$user" = - ] && user=; [ "$group" = - ] && group= + case "$type" in + d|q|D) mkdir -p "$path" ;; + f) [ -e "$path" ] || : > "$path" ;; + *) echo "systemd-tmpfiles shim: skip $type $path"; continue ;; + esac + [ -n "$mode" ] && chmod "$mode" "$path" + [ -n "$user" ] && chown "$user${group:+:$group}" "$path" + done < "$f" +done diff --git a/userspace/void/core-services/03-filesystems.sh b/userspace/void/core-services/03-filesystems.sh new file mode 100644 index 0000000..abcce75 --- /dev/null +++ b/userspace/void/core-services/03-filesystems.sh @@ -0,0 +1,9 @@ +# AGNOS override of runit-void's 03-filesystems.sh. +# The rootfs is a read-only A/B system partition: never fsck or remount it rw, +# and never drop to an emergency shell when an optional mount fails. + +[ -n "$IS_CONTAINER" ] && return 0 + +msg "AGNOS: leaving rootfs as mounted by the kernel" +msg "Mounting all non-network filesystems..." +mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev || msg_warn "mount -a had failures, continuing" diff --git a/userspace/void/core-services/04-agnos.sh b/userspace/void/core-services/04-agnos.sh new file mode 100644 index 0000000..c926a55 --- /dev/null +++ b/userspace/void/core-services/04-agnos.sh @@ -0,0 +1,17 @@ +# AGNOS: read-only rootfs setup, replaces fs_setup.service + serial-hostname.service +[ -n "$IS_CONTAINER" ] && return 0 + +# /var is a tmpfs; repopulate from the build-time copy. The xbps pkgdb stays on the rootfs. +if mountpoint -q /var; then + msg "AGNOS: populating /var from /usr/default/var..." + tar -C /usr/default/var --exclude=./db/xbps --exclude=./cache -cf - . | tar -C /var -xpf - + mkdir -p /var/db /var/cache + ln -sfn /usr/default/var/db/xbps /var/db/xbps +fi +[ -L /var/service ] || ln -sfn ../run/runit/runsvdir/current /var/service + +msg "AGNOS: fs_setup..." +/usr/comma/fs_setup.sh + +msg "AGNOS: hostname from serial..." +/usr/comma/set-hostname.sh || msg_warn "set-hostname failed" diff --git a/userspace/void/finalize.sh b/userspace/void/finalize.sh new file mode 100755 index 0000000..91ac98e --- /dev/null +++ b/userspace/void/finalize.sh @@ -0,0 +1,68 @@ +#!/bin/bash -e +# Void-specific config fixups applied after the userspace/root overlay. +set -x + +# --- core-services / stage 2 overrides for the read-only rootfs --- +install -m 644 /tmp/agnos/void/core-services/03-filesystems.sh /etc/runit/core-services/03-filesystems.sh +install -m 644 /tmp/agnos/void/core-services/04-agnos.sh /etc/runit/core-services/04-agnos.sh +install -m 755 /tmp/agnos/void/runit-2 /etc/runit/2 +# 4.9 kernel: the cgroup2 nsdelegate mount option doesn't exist +sed -i 's/^#\?CGROUP_MODE=.*/CGROUP_MODE=legacy/' /etc/rc.conf +grep -q '^CGROUP_MODE=legacy' /etc/rc.conf || echo 'CGROUP_MODE=legacy' >> /etc/rc.conf + +# --- systemd compatibility shims --- +install -m 755 /tmp/agnos/void/bin/* /usr/local/bin/ + +# --- NetworkManager: no systemd-resolved --- +sed -i 's/^dns=systemd-resolved/dns=default/' /etc/NetworkManager/NetworkManager.conf + +# --- sshd: sftp-server path --- +sed -i 's|/usr/lib/openssh/sftp-server|/usr/libexec/sftp-server|' /etc/ssh/sshd_config + +# --- iptables: legacy xtables backend (4.9 kernel has no nftables), rules from iptables-persistent's path --- +for t in iptables iptables-restore iptables-save ip6tables ip6tables-restore ip6tables-save; do + ln -sf xtables-legacy-multi /usr/bin/$t +done +ln -sf rules.v4 /etc/iptables/iptables.rules + +# --- python: venv uses a uv-managed 3.12, not the system python --- +sed -i 's/^export UV_PYTHON_PREFERENCE=only-system/export UV_PYTHON_PREFERENCE=only-managed/' /etc/profile + +# --- adbd: bundled Ubuntu libs (Android libs' RUNPATH points at this dir) --- +patchelf --force-rpath --set-rpath /usr/lib/aarch64-linux-gnu/android /usr/comma/adbd + +# ldconfig rewrites SONAME links back to glvnd/Mesa, so run it before the Adreno links +# (same ordering constraint as Dockerfile.agnos; nothing may run ldconfig afterwards) +ldconfig + +# --- graphics: Adreno EGL/GLES/GBM instead of glvnd/Mesa (Void has no multiarch dir) --- +lib=/usr/lib/aarch64-linux-gnu +test -f $lib/libEGL_adreno.so +test -f $lib/libGLESv2_adreno.so +ln -sf $lib/libEGL_adreno.so $lib/libEGL.so.1 +ln -sf $lib/libGLESv2_adreno.so $lib/libGLESv2.so.2 +cp $lib/libgbm.so $lib/libgbm.so.1 +for l in libEGL.so.1 libGLESv2.so.2 libgbm.so.1; do + # /usr/lib wins over ld.so.conf dirs only via the cache; make it unambiguous + ln -sf aarch64-linux-gnu/$l /usr/lib/$l +done +rm -f /usr/lib/libgallium-*.so /usr/lib/libEGL_mesa.so* /usr/lib/libGLX_mesa.so* /usr/lib/libgbm.so.1.* +rm -rf /usr/lib/dri /usr/lib/gbm +rm -f /usr/share/glvnd/egl_vendor.d/50_mesa.json + +# --- runit services --- +/tmp/agnos/void/services.sh + +# --- read-only rootfs (readonly_setup.sh equivalent) --- +rm -rf /etc/timezone /etc/localtime +ln -s /data/etc/timezone /etc/timezone +ln -s /data/etc/localtime /etc/localtime +rm -f /etc/ssh/ssh_host* +rm -rf /etc/NetworkManager/system-connections +ln -s /data/etc/NetworkManager/system-connections /etc/NetworkManager/system-connections +mkdir -p /usr/default +rm -rf /var/cache/* +mv /var /usr/default && mkdir /var +mv /home /usr/default && mkdir /home +rm -rf /tmp && mkdir -m 1777 /tmp +mkdir -p /cache /firmware /dsp /rwtmp diff --git a/userspace/void/python_setup.sh b/userspace/void/python_setup.sh new file mode 100755 index 0000000..b76a74c --- /dev/null +++ b/userspace/void/python_setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash -e +# Void ships Python 3.14 only; openpilot pins >=3.12.3,<3.13. +# Use a uv-managed (python-build-standalone) 3.12 for the venv. +export XDG_DATA_HOME="/usr/local" +export UV_PYTHON_INSTALL_DIR="/usr/local/uv/python" +uv python install 3.12 +uv venv $XDG_DATA_HOME/venv --seed --python-preference only-managed --python=3.12 diff --git a/userspace/void/runit-2 b/userspace/void/runit-2 new file mode 100755 index 0000000..b89dcc4 --- /dev/null +++ b/userspace/void/runit-2 @@ -0,0 +1,13 @@ +#!/bin/sh +# AGNOS override of runit-void's stage 2: no runsvchdir (it writes to the +# read-only /etc). /etc/runit/runsvdir/current -> default is set at build time. + +PATH=/usr/bin:/usr/sbin + +[ -x /etc/rc.local ] && /etc/rc.local + +mkdir -p /run/runit/runsvdir +ln -sfn /etc/runit/runsvdir/current /run/runit/runsvdir/current + +exec env - PATH=$PATH \ + runsvdir -P /run/runit/runsvdir/current 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................' diff --git a/userspace/void/services.sh b/userspace/void/services.sh new file mode 100755 index 0000000..2e1d2fd --- /dev/null +++ b/userspace/void/services.sh @@ -0,0 +1,220 @@ +#!/bin/bash -e +# Void/runit equivalent of services.sh. Generates /etc/sv/ from our +# systemd units (by hand, see notes) and links them into the default runlevel. +# - Type=oneshot -> run once, then park in `pause` so runsv doesn't respawn +# - .path units -> poll loop +# - .timer -> sleep loop +# - User=comma -> setpriv (no PAM, so limits.conf is NOT applied) +# - fs_setup, serial-hostname -> /etc/runit/core-services/04-agnos.sh (stage 1) +SV=/etc/sv +RUNSVDIR=/etc/runit/runsvdir/default + +as_comma='setpriv --reuid=comma --regid=comma --init-groups env HOME=/home/comma USER=comma LOGNAME=comma' + +mk() { # name: create dir, vlogger log service, supervise symlinks onto /run + local n=$1 + mkdir -p "$SV/$n/log" + cat > "$SV/$n/log/run" < "$SV/$n/run" <&1 +[ -e /run/agnos-oneshot.$n ] && exec pause +$* +echo "$n: exited \$?" +touch /run/agnos-oneshot.$n +exec pause +EOF +} + +simple() { # name, command (exec'd) + local n=$1; shift + mk "$n" + cat > "$SV/$n/run" <&1 +exec $* +EOF +} + +oneshot init-qcom /usr/comma/init.qcom.sh +oneshot cdsp "sh -c 'echo 1 > /sys/kernel/boot_cdsp/boot'" +oneshot gpio /usr/comma/gpio.sh +oneshot sound /usr/comma/sound/sound_init.sh +oneshot screen_calibration /usr/comma/screen_calibration.py +oneshot agnos-debug "$as_comma /usr/comma/debug.py" + +simple wifi /usr/comma/wifi.sh +simple varwatch /usr/local/venv/bin/python -u /usr/comma/varwatch.py +simple power_monitor /usr/local/venv/bin/python -u /usr/comma/power_monitor.py +simple brightnessd /usr/local/venv/bin/python -u /usr/comma/brightnessd.py +simple adbd /usr/comma/adbd + +mk power_drop_monitor +cat > $SV/power_drop_monitor/run <<'EOF' +#!/bin/sh +exec 2>&1 +# ExecCondition: only on tici/tizi; otherwise stay down +grep -q -E "tici|tizi" /sys/firmware/devicetree/base/model || { sv down power_drop_monitor; exec pause; } +exec /usr/local/venv/bin/python -u /usr/comma/power_drop_monitor.py +EOF + +mk avahi-ssh-publish +cat > $SV/avahi-ssh-publish/run <&1 +sv check avahi-daemon >/dev/null || exit 1 +sleep 5 # RestartSec=5 +exec $as_comma /usr/comma/avahi-publish-service.sh +EOF + +mk lte +cat > $SV/lte/run <<'EOF' +#!/bin/sh +# RemainAfterExit service with an ExecStop +exec 2>&1 +sv check gpio >/dev/null || exit 1 +[ -e /run/agnos-oneshot.lte ] || { /usr/comma/lte/lte.sh start; touch /run/agnos-oneshot.lte; } +exec pause +EOF +cat > $SV/lte/finish <<'EOF' +#!/bin/sh +# runs on "sv down lte" or shutdown (and on any pause exit): ExecStop +[ -e /run/agnos-oneshot.lte ] || exit 0 +/usr/comma/lte/lte.sh stop +rm -f /run/agnos-oneshot.lte +EOF + +mk modem +cat > $SV/modem/run <&1 +sv start lte >/dev/null || exit 1 +sleep 5 # RestartSec=5 +exec $as_comma /usr/bin/python3 -u /usr/comma/modem.py +EOF +cat > $SV/modem/finish <<'EOF' +#!/bin/sh +rm -f /dev/shm/modem +EOF + +mk magic +cat > $SV/magic/run <&1 +chgrp gpu /dev/ion /dev/kgsl-3d0 +chmod 660 /dev/ion /dev/kgsl-3d0 +mkdir -p /data/misc/display || true +echo 0 > /data/misc/display/sdm_dbg_cfg.txt || true +echo 0 > /data/misc/display/gbm_dbg_cfg.txt || true +exec $as_comma /bin/bash -c "source /etc/profile && exec /usr/local/venv/bin/python -u /usr/comma/magic.py" +EOF + +mk comma +cat > $SV/comma/run <&1 +[ -e /run/agnos-oneshot.comma ] && exec pause +ulimit -r 100 +ulimit -e 30 +$as_comma /usr/bin/tmux new-session -s comma -d /usr/comma/comma.sh +echo "comma: tmux started \$?" +touch /run/agnos-oneshot.comma +exec pause +EOF +cat > $SV/comma/finish </dev/null)" = "down" ] || [ -e down ] || exit 0 +$as_comma /usr/bin/tmux kill-session -t comma 2>/dev/null +rm -f /run/agnos-oneshot.comma +exit 0 +EOF + +# Void's sshd service, with ssh.service.d/override.conf (host keys on /data) +cat > $SV/sshd/run <<'EOF' +#!/bin/sh +exec 2>&1 +mkdir -p /data/etc/ssh/ /run/sshd +ssh-keygen -A -f /data +/usr/bin/sshd -t || exit 1 +exec /usr/bin/sshd -D +EOF + +watcher() { # name, param file, script + mk "$1" + cat > "$SV/$1/run" <&1 +last="" +while :; do + cur=\$(stat -c '%Y %s %i' "$2" 2>/dev/null) + if [ "\$cur" != "\$last" ]; then + $3 + last="\$cur" + fi + sleep 2 +done +EOF +} +watcher ssh-param-watcher /data/params/d/SshEnabled /usr/comma/set_ssh.sh +watcher adb-param-watcher /data/params/d/AdbEnabled /usr/comma/set_adb.sh + +mk logrotate-hourly +cat > $SV/logrotate-hourly/run <<'EOF' +#!/bin/sh +# logrotate-hourly.timer (OnCalendar=hourly) +exec 2>&1 +while :; do + sleep 3600 + /usr/bin/logrotate /etc/logrotate.conf +done +EOF + +# Serial console. The systemd drop-in forced a hangup with stty hupcl. +mk agetty-ttyMSM0 +rm -rf $SV/agetty-ttyMSM0/log +cat > $SV/agetty-ttyMSM0/run <<'EOF' +#!/bin/sh +/bin/stty -F /dev/ttyMSM0 hupcl +exec chpst -P agetty -L -8 ttyMSM0 115200 vt100 +EOF +cat > $SV/agetty-ttyMSM0/finish <<'EOF' +#!/bin/sh +exec utmpset -w ttyMSM0 +EOF + +chmod 755 $SV/*/run $SV/*/log/run +chmod 755 $SV/*/finish 2>/dev/null || true + +# --- runlevel --- +rm -f $RUNSVDIR/* +ENABLED=( + udevd dbus polkitd rsyslogd NetworkManager chronyd avahi-daemon + sshd agetty-ttyMSM0 + init-qcom cdsp wifi gpio sound screen_calibration agnos-debug + magic comma brightnessd power_monitor varwatch +) +# On demand / riskier: linked but start "down" (sv up X works at runtime; +# runsvchdir/sv enable can't write the read-only /etc). +DOWN=( + adbd lte modem power_drop_monitor ssh-param-watcher adb-param-watcher + logrotate-hourly avahi-ssh-publish +) +for s in "${ENABLED[@]}" "${DOWN[@]}"; do + test -d "$SV/$s" || { echo "missing service $s"; exit 1; } + ln -sfn "$SV/$s" "$RUNSVDIR/$s" +done +for s in "${DOWN[@]}"; do touch "$SV/$s/down"; done