diff --git a/debian-live-usb/build.sh b/debian-live-usb/build.sh new file mode 100755 index 00000000..3defc2c1 --- /dev/null +++ b/debian-live-usb/build.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# Build WROLPi Portable — a bootable amd64 Live USB image. +# +# Output: an iso-hybrid image written to this directory. Users flash it to a +# USB drive (Etcher / Rufus / dd / Raspberry Pi Imager) and boot it on any +# x86 PC. Unlike ../debian-live-config/, this build is NOT an installer — it +# runs the full WROLPi stack live from the USB. Phase 2 will add a persistence +# partition so the Postgres DB and media directory survive reboots. +# +# https://live-team.pages.debian.net/live-manual/html/live-manual/index.en.html + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +BUILD_DIR=/var/tmp/wrolpi-build-portable + +Help() { + echo "Build WROLPi Portable Live USB image." + echo + echo "Syntax: build.sh [-h] [-b BRANCH]" + echo "options:" + echo "h Print this help." + echo "b Build from this git BRANCH (default: 'release')." + echo +} + +BRANCH="release" +while getopts ":hb:" option; do + case $option in + h) Help; exit ;; + b) BRANCH="${OPTARG}" ;; + *) echo "Error: Invalid option"; exit 1 ;; + esac +done + +# Get version from the target git branch (not local checkout) so output filename matches actual content. +VERSION=$(curl -sL "https://raw.githubusercontent.com/lrnselfreliance/wrolpi/${BRANCH}/wrolpi/version.txt") +if [ -z "${VERSION}" ]; then + echo "ERROR: Could not fetch version from branch '${BRANCH}'" + exit 1 +fi +echo "Building WROLPi Portable version: ${VERSION} from branch: ${BRANCH}" + +# Re-execute this script if it wasn't called with sudo. +if [ $EUID != 0 ]; then + sudo "$0" "$@" + exit $? +fi + +# Clear out old builds. +[ -d "${BUILD_DIR}" ] && rm -rf "${BUILD_DIR}" +mkdir "${BUILD_DIR}" +cd "${BUILD_DIR}" || (echo "Work directory must exist" && exit 1) +set -e + +# Pure live config — no --debian-installer flags. The resulting image boots +# directly into a running WROLPi system, never offering to install to disk. +# noswap prevents the live system from activating swap partitions found on +# the host machine's internal disks (ethereal hygiene). +lb config \ + --binary-images iso-hybrid \ + --mode debian \ + --architectures amd64 \ + --linux-flavours amd64 \ + --distribution bookworm \ + --archive-areas "main contrib non-free non-free-firmware" \ + --updates true \ + --security true \ + --apt-recommends false \ + --firmware-binary true \ + --firmware-chroot true \ + --win32-loader false \ + --checksums sha512 \ + --clean \ + --color \ + --linux-packages "linux-image linux-headers" \ + --bootappend-live "boot=live components quiet splash noswap" \ + --iso-volume "WROLPi Portable v${VERSION}" \ + --iso-application "WROLPi Portable v${VERSION}" \ + --iso-preparer WROLPi \ + --iso-publisher https://wrolpi.org + +rsync -a "${SCRIPT_DIR}/config" "${BUILD_DIR}/" + +# Write branch config for hook script. +mkdir -p "${BUILD_DIR}/config/includes.chroot/opt/wrolpi-blobs" +echo "${BRANCH}" > "${BUILD_DIR}/config/includes.chroot/opt/wrolpi-branch" + +# Cache directory for downloaded blobs (persists across builds). +BLOB_CACHE="${SCRIPT_DIR}/files" +mkdir -p "${BLOB_CACHE}" + +# Download map fonts (skip if cached). +if [ ! -f "${BLOB_CACHE}/map-fonts.tar.gz" ]; then + echo "Downloading map fonts..." + mkdir -p /tmp/map-fonts-dl + curl -fsSL https://github.com/protomaps/basemaps-assets/archive/refs/heads/main.tar.gz \ + | tar -xz --strip-components=1 -C /tmp/map-fonts-dl basemaps-assets-main/fonts + tar -czf "${BLOB_CACHE}/map-fonts.tar.gz" -C /tmp/map-fonts-dl fonts + rm -rf /tmp/map-fonts-dl +fi +[[ -f "${BLOB_CACHE}/map-fonts.tar.gz" && -s "${BLOB_CACHE}/map-fonts.tar.gz" ]] || \ + (echo "ERROR: Failed to download map fonts!" && exit 1) + +# Download map overview (skip if cached). +if [ ! -f "${BLOB_CACHE}/map-overview.pmtiles" ]; then + echo "Downloading map overview..." + curl -fsSL https://wrolpi.nyc3.cdn.digitaloceanspaces.com/maps/map-overview.pmtiles \ + -o "${BLOB_CACHE}/map-overview.pmtiles" +fi +[[ -f "${BLOB_CACHE}/map-overview.pmtiles" && -s "${BLOB_CACHE}/map-overview.pmtiles" ]] || \ + (echo "ERROR: Failed to download map overview!" && exit 1) + +# Copy cached blobs into the live image. +cp "${BLOB_CACHE}/map-fonts.tar.gz" "${BUILD_DIR}/config/includes.chroot/opt/wrolpi-blobs/" +cp "${BLOB_CACHE}/map-overview.pmtiles" "${BUILD_DIR}/config/includes.chroot/opt/wrolpi-blobs/" + +time nice -n 18 lb build 2>&1 | tee "${SCRIPT_DIR}/build.log" + +grep "9999-wrolpi.hook.chroot completed" "${SCRIPT_DIR}/build.log" >/dev/null 2>&1 || (echo "build hook failed!" && exit 1) + +cp "${BUILD_DIR}"/*iso "${SCRIPT_DIR}/" || (echo "Build failed. No ISOs were found!" && exit 1) +chmod 644 "${SCRIPT_DIR}"/*iso +chown 1000:1000 "${SCRIPT_DIR}"/*iso +DEST="${SCRIPT_DIR}/WROLPi-Portable-v${VERSION}-amd64.iso" +[ -f "${DEST}" ] && (echo "Removing conflicting ISO" && rm "${DEST}") +mv "${SCRIPT_DIR}"/*.iso "${DEST}" + +echo "Build has completed. ISO output ${DEST}" diff --git a/debian-live-usb/config/hooks/normal/9999-wrolpi.hook.chroot b/debian-live-usb/config/hooks/normal/9999-wrolpi.hook.chroot new file mode 100755 index 00000000..558552a5 --- /dev/null +++ b/debian-live-usb/config/hooks/normal/9999-wrolpi.hook.chroot @@ -0,0 +1,153 @@ +#!/usr/bin/bash +# Chroot hook for WROLPi Portable — composes the full WROLPi stack into the +# live system during live-build's chroot phase. Closely mirrors the installer +# ISO hook in ../../../debian-live-config/config/hooks/normal/, with two +# Portable-specific additions: bake in pmtiles/tippecanoe (offline-first +# first-boot) and drop the default Postgres cluster (Phase 2 firstboot will +# recreate it on the persistence partition). + +set -e +set -x + +# Generate and set default locale to avoid perl warnings. +# Users can change this later via: sudo dpkg-reconfigure locales +sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen +locale-gen +echo 'LANG=en_US.UTF-8' > /etc/default/locale +export LANG=en_US.UTF-8 + +cat >>/etc/profile <<'EOF' +alias ll='ls -lh' +alias la='ll -a' +EOF + +git --version + +WROLPI_BRANCH="release" +[ -f /opt/wrolpi-branch ] && WROLPI_BRANCH=$(cat /opt/wrolpi-branch) +git clone -b "${WROLPI_BRANCH}" https://github.com/lrnselfreliance/wrolpi.git /opt/wrolpi +rm -f /opt/wrolpi-branch + +# Install webapp. +cd /opt/wrolpi/app +npm --version +node --version +npm install + +# Install Node console commands. +npm i -g serve@12.0.1 single-file-cli@2.0.73 readability-extractor@0.0.6 + +# Install Deno runtime. +DENO_VERSION="v2.2.4" +curl -fsSL "https://github.com/denoland/deno/releases/download/${DENO_VERSION}/deno-x86_64-unknown-linux-gnu.zip" -o /tmp/deno.zip +unzip /tmp/deno.zip -d /usr/local/bin/ +chmod +x /usr/local/bin/deno +rm /tmp/deno.zip +deno --version + +# Install Python requirements. Try multiple times because pypi may stop responding. +python3 --version +python3 -m venv /opt/wrolpi/venv +/opt/wrolpi/venv/bin/pip3 install -r /opt/wrolpi/requirements.txt || + /opt/wrolpi/venv/bin/pip3 install -r /opt/wrolpi/requirements.txt || + /opt/wrolpi/venv/bin/pip3 install -r /opt/wrolpi/requirements.txt + +# Install Controller with separate venv. +echo "Pre-installing WROLPi Controller..." +python3 -m venv /opt/wrolpi/controller/venv +/opt/wrolpi/controller/venv/bin/pip install --upgrade pip +/opt/wrolpi/controller/venv/bin/pip install -r /opt/wrolpi/controller/requirements.txt || + /opt/wrolpi/controller/venv/bin/pip install -r /opt/wrolpi/controller/requirements.txt || + /opt/wrolpi/controller/venv/bin/pip install -r /opt/wrolpi/controller/requirements.txt +echo "Controller pre-installed" + +# Install Caddy from official apt repo. +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list +apt-get update +apt-get install -y caddy + +# Configure Caddy. +mkdir -p /etc/caddy +cp /opt/wrolpi/etc/raspberrypios/Caddyfile /etc/caddy/Caddyfile +mkdir -p /var/www +cp /opt/wrolpi/etc/raspberrypios/50x.html /var/www/50x.html + +# WROLPi needs a few privileged commands. +cp /opt/wrolpi/etc/raspberrypios/90-wrolpi /etc/sudoers.d/90-wrolpi +chmod 0440 /etc/sudoers.d/90-wrolpi +# Verify this new file is valid. +visudo -c -f /etc/sudoers.d/90-wrolpi + +mkdir /media/wrolpi + +# Verify blob files were copied in by includes.chroot (downloaded by build.sh). +[[ -f /opt/wrolpi-blobs/map-fonts.tar.gz && -s /opt/wrolpi-blobs/map-fonts.tar.gz ]] || \ + (echo "ERROR: map-fonts.tar.gz missing from /opt/wrolpi-blobs!" && exit 1) +[[ -f /opt/wrolpi-blobs/map-overview.pmtiles && -s /opt/wrolpi-blobs/map-overview.pmtiles ]] || \ + (echo "ERROR: map-overview.pmtiles missing from /opt/wrolpi-blobs!" && exit 1) + +# Bake in pmtiles CLI so first boot does not need internet. +# Mirrors scripts/install_protomaps.sh; duplicated here because that script's +# migration logic relies on a running systemd, which is not present in chroot. +PMTILES_VERSION="v1.30.1" +curl -fsSL "https://github.com/protomaps/go-pmtiles/releases/download/${PMTILES_VERSION}/go-pmtiles_${PMTILES_VERSION#v}_Linux_amd64.tar.gz" \ + | tar -xz -C /usr/local/bin/ pmtiles +chmod +x /usr/local/bin/pmtiles +pmtiles version + +# Build and bake in tippecanoe (used to build map search indexes). +TIPPECANOE_VERSION="2.79.0" +TIPPECANOE_BUILD_DIR=$(mktemp -d) +curl -fsSL "https://github.com/felt/tippecanoe/archive/refs/tags/${TIPPECANOE_VERSION}.tar.gz" \ + | tar -xz -C "${TIPPECANOE_BUILD_DIR}" --strip-components=1 +(cd "${TIPPECANOE_BUILD_DIR}" && make -j"$(nproc)" && make install) +rm -rf "${TIPPECANOE_BUILD_DIR}" +tippecanoe --version + +# Install WROLPi Help. +/opt/wrolpi/scripts/install_help_service.sh + +cp /opt/wrolpi/etc/raspberrypios/wrolpi-*.service /etc/systemd/system/ +cp /opt/wrolpi/etc/raspberrypios/wrolpi-*.timer /etc/systemd/system/ +cp /opt/wrolpi/etc/raspberrypios/wrolpi.target /etc/systemd/system/ +# Copy MOTD scripts, delete original. +cp /opt/wrolpi/etc/raspberrypios/motd/20-wrolpi.motd /etc/update-motd.d/20-wrolpi +cp /opt/wrolpi/etc/raspberrypios/motd/30-wrolpi-first.motd /etc/update-motd.d/30-wrolpi +chmod +x /etc/update-motd.d/* +truncate -s 0 /etc/motd + +# NetworkManager for the hotspot. +systemctl enable NetworkManager + +# Enable iperf3 for speed testing. +systemctl enable iperf3 +# Enable first startup script to generate certificates and start Caddy. +systemctl enable wrolpi-first-startup.service +# Enable Controller service to start on boot. +systemctl enable wrolpi-controller.service +# Enable weekly certificate renewal check. +systemctl enable wrolpi-cert-renew.timer + +# Drop the default Postgres cluster created by the postgresql-15 package. +# On first boot, the persistence partition will be bind-mounted over +# /var/lib/postgresql; with the default cluster baked into the squashfs, the +# mount would mask it and Postgres would fail to start against an empty data +# dir. Phase 2's firstboot service creates a fresh cluster directly on the +# persistence partition. +pg_dropcluster --stop 15 main + +# Protect blob files from accidental modification or deletion. +if [ -d /opt/wrolpi-blobs ] && ls /opt/wrolpi-blobs/* >/dev/null 2>&1; then + chown -R root:root /opt/wrolpi-blobs + chmod 444 /opt/wrolpi-blobs/* + chattr +i /opt/wrolpi-blobs/* +fi + +set +x + +echo +echo "======================================================================" +echo "9999-wrolpi.hook.chroot completed" +echo "======================================================================" +echo diff --git a/debian-live-usb/config/package-lists/desktop.list.chroot b/debian-live-usb/config/package-lists/desktop.list.chroot new file mode 100644 index 00000000..a44ac60c --- /dev/null +++ b/debian-live-usb/config/package-lists/desktop.list.chroot @@ -0,0 +1,2 @@ +task-xfce-desktop +task-ssh-server diff --git a/debian-live-usb/config/package-lists/wrolpi.list.chroot b/debian-live-usb/config/package-lists/wrolpi.list.chroot new file mode 100644 index 00000000..e2f73c3b --- /dev/null +++ b/debian-live-usb/config/package-lists/wrolpi.list.chroot @@ -0,0 +1,82 @@ +apt-transport-https +aria2 +autoconf +bind9-dnsutils +build-essential +bzip2 +ca-certificates +calibre +cargo +catdoc +chromium +cpufrequtils +curl +ethtool +exfat-fuse +fdupes +ffmpeg +firefox-esr +fonts-noto-cjk +fonts-noto-hinted +fonts-noto-unhinted +gcc +gcc-doc +git +git-doc +gnupg-agent +gpsd +hostapd +htop +iotop +iperf3 +kiwix +kiwix-tools +libboost-all-dev +libbz2-dev +libcairo2-dev +libcurl4-gnutls-dev +libffi-dev +libfreetype6-dev +libgeos++-dev +libgeos-dev +libicu-dev +libpq-dev +libproj-dev +libreoffice +libsqlite3-dev +libssl-dev +libtiff5-dev +libtool +libxml2-dev +netcat-openbsd +network-manager +nicstat +nodejs +npm +ntfs-3g +pkg-config +postgresql-15 +postgresql-doc +pv +python3-dev +python3-doc +python3-full +python3-pip +python3-venv +rsync +samba +smartmontools +software-properties-common +sudo +sysstat +tar +tcpdump +tmux +unrar-free +unzip +vim +vlc +wget +wpasupplicant +zim-tools +zlib1g-dev diff --git a/debian-live-usb/files/.gitignore b/debian-live-usb/files/.gitignore new file mode 100644 index 00000000..38eee4b8 --- /dev/null +++ b/debian-live-usb/files/.gitignore @@ -0,0 +1,2 @@ +*.pmtiles +*.tar.gz