# elizaOS Live — Justfile recipes
#
# The ISO build is containerized: any host with Docker (Linux / macOS /
# Windows WSL2 / CI) builds an identical ISO with no VM, no Vagrant, no
# host-specific setup. See build.sh / Dockerfile / build-iso.sh.
#
# The dev loop has three speeds — pick the slowest one your change needs:
#
#   1. App work (the elizaOS desktop)   → develop the app on your host,
#      normal hot-reload. Never touches the ISO. ~90% of dev time.
#   2. OS-level config (branding,      → `just nspawn` — boots the built
#      systemd units, hooks, files)      chroot/ in seconds for non-GUI
#                                        sanity checks. No ISO build.
#   3. Full integration test           → `just binary` — incremental
#                                        ISO rebuild (~10 min), or
#                                        `just build` for a clean one.
#
# A cold `just build` is a once-in-a-while thing, not the dev loop.

default:
    @just --list

# --- Preflight --------------------------------------------------------------

# The only host requirement is Docker — the container is the build env.
setup:
    @command -v docker >/dev/null 2>&1 || { echo "Install Docker first — it is the only host requirement."; exit 1; }
    @docker info >/dev/null 2>&1 || { echo "Docker is installed but not running / not accessible to this user."; exit 1; }
    @echo "Docker OK — run 'just config' for a go/no-go, or 'just build' for a full ISO."

# --- Build ------------------------------------------------------------------

# Build or stage the elizaOS Electrobun app artifact for the ISO. If the
# existing app build is present, this only rsyncs it into live-build's
# chroot_local-includes overlay. Set ELIZAOS_BUILD_APP=1 to allow
# this recipe to run the heavier app build when the artifact is missing.
elizaos-app:
    #!/usr/bin/env bash
    set -euo pipefail
    eliza_root="$(git rev-parse --show-toplevel)"
    outer_root="$(cd "${eliza_root}/.." && pwd)"
    app_out="${ELIZAOS_APP_ARTIFACT:-${eliza_root}/packages/app-core/platforms/electrobun/build/dev-linux-x64/elizaOS-dev}"
    stage="tails/config/chroot_local-includes/usr/share/elizaos/elizaos-app"

    ensure_plugin_runtime_dist() {
        local package_rel="$1"
        local mode="$2"
        local package_dir="${eliza_root}/${package_rel}"
        local dist_index="${package_dir}/dist/index.js"

        if [ -s "${dist_index}" ]; then
            return 0
        fi

        echo "Building ${package_rel} runtime dist for elizaOS Live"
        case "${mode}" in
            package-build)
                if [ ! -x "${eliza_root}/node_modules/.bin/tsup" ] || [ ! -x "${eliza_root}/node_modules/.bin/tsc" ]; then
                    echo "Installing workspace build dependencies for ${package_rel}"
                    ( cd "${eliza_root}" && bun install --no-frozen-lockfile --ignore-scripts )
                fi
                ( cd "${package_dir}" && bun run build )
                ;;
            package-js)
                if [ ! -x "${eliza_root}/node_modules/.bin/tsup" ]; then
                    echo "Installing workspace build dependencies for ${package_rel}"
                    ( cd "${eliza_root}" && bun install --no-frozen-lockfile --ignore-scripts )
                fi
                ( cd "${package_dir}" && bun run build:js )
                ;;
            tsup-index)
                if [ ! -x "${eliza_root}/node_modules/.bin/tsup" ]; then
                    echo "Installing workspace build dependencies for ${package_rel}"
                    ( cd "${eliza_root}" && bun install --no-frozen-lockfile --ignore-scripts )
                fi
                ( cd "${package_dir}" && bunx tsup src/index.ts --format esm --clean )
                ;;
            bun-runtime-index)
                ( cd "${package_dir}" && \
                    rm -rf dist && \
                    bun build src/index.ts \
                        --outdir dist \
                        --target=node \
                        --format=esm \
                        --sourcemap=external \
                        --external '@elizaos/agent' \
                        --external '@elizaos/app-core' \
                        --external '@elizaos/app-core/*' \
                        --external '@elizaos/core' \
                        --external '@elizaos/plugin-registry' \
                        --external '@elizaos/shared' \
                        --external '@elizaos/vault' \
                        --external dotenv \
                        --external 'node:*' \
                        --external 'bun:*' )
                ;;
            *)
                echo "Unknown runtime package build mode: ${mode}" >&2
                exit 1
                ;;
        esac

        test -s "${dist_index}" || {
            echo "missing ${dist_index} after runtime package build" >&2
            exit 1
        }
    }

    if [ ! -x "${app_out}/bin/launcher" ]; then
        if [ "${ELIZAOS_BUILD_APP:-0}" != "1" ]; then
            echo "elizaOS app build not found at ${app_out}/bin/launcher"
            echo "Build it separately, set ELIZAOS_APP_ARTIFACT, or rerun with ELIZAOS_BUILD_APP=1 to permit this recipe to build it."
            exit 1
        fi
        echo "elizaOS app build not found; ELIZAOS_BUILD_APP=1 so building it now"
        ( cd "${eliza_root}" && bun install --no-frozen-lockfile --ignore-scripts )
        if [ -f "${outer_root}/package.json" ] && [ -d "${outer_root}/eliza" ]; then
            ( cd "${outer_root}" && bun install --no-frozen-lockfile --ignore-scripts )
            ( cd "${outer_root}" && bun install --cwd eliza --no-frozen-lockfile --ignore-scripts )
            ( cd "${outer_root}" && ELIZAOS_ELIZA_SOURCE=local node scripts/setup-upstreams.mjs )
            if [ -d "${outer_root}/eliza/packages/electrobun-carrots" ]; then
                ( cd "${outer_root}/eliza/packages/electrobun-carrots" && bun run build )
            fi
            ( cd "${outer_root}" && ELIZAOS_ELIZA_SOURCE=local bun run build:desktop )
        else
            ( cd "${eliza_root}" && ELIZAOS_ELIZA_SOURCE=local node packages/app-core/scripts/setup-upstreams.mjs )
            if [ -d "${eliza_root}/packages/electrobun-carrots" ]; then
                ( cd "${eliza_root}/packages/electrobun-carrots" && bun run build )
            fi
            ( cd "${eliza_root}" && \
                ELIZA_APP_NAME=elizaOS \
                ELIZA_APP_ID=org.elizaos.app \
                ELIZA_URL_SCHEME=elizaos \
                ELIZA_NAMESPACE=elizaos \
                bun run --cwd packages/app-core/platforms/electrobun build )
        fi
    fi
    test -x "${app_out}/bin/launcher" || { echo "missing ${app_out}/bin/launcher"; exit 1; }
    ensure_plugin_runtime_dist "plugins/plugin-health" package-js
    ensure_plugin_runtime_dist "plugins/plugin-calendly" tsup-index
    ensure_plugin_runtime_dist "plugins/plugin-registry" package-js
    ensure_plugin_runtime_dist "plugins/plugin-app-manager" bun-runtime-index
    if [ -e "${stage}" ] && ! rm -rf "${stage}"; then
        sudo -n rm -rf "${stage}"
    fi
    mkdir -p "${stage}"
    rsync -a --delete "${app_out}/" "${stage}/"
    node scripts/prepare-elizaos-app-overlay.mjs "${stage}"
    echo "staged elizaOS app at ${stage}"

# Full clean ISO build → out/. ~1.5h cold, much faster once the
# apt-cacher-ng volume is warm. This is the release-quality build.
build: elizaos-app
    ./build.sh build

# Like 'build' but with low-compression squashfs — faster mksquashfs,
# larger ISO. For dev iteration when you just need a bootable image.
build-fast: elizaos-app
    MT_FAST=1 ./build.sh build

# Low-CPU full build for hot laptops / parallel dev work. Override the
# cap with ELIZAOS_BUILD_CPUS=3 just build-cool. This defaults to
# demo-friendly docs behavior; set ELIZAOS_SKIP_WEBSITE=0 to rebuild docs.
build-cool: elizaos-app
    ELIZAOS_SKIP_WEBSITE="${ELIZAOS_SKIP_WEBSITE:-1}" scripts/run-cool-build.sh build

# Fastest full demo build: keep the Tails OS build, low-compression ISO,
# and CPU cap, but skip rebuilding the bundled offline website/docs.
# Use release `just build` when the local Tails documentation must be exact.
build-demo: elizaos-app
    ELIZAOS_SKIP_WEBSITE=1 scripts/run-cool-build.sh build

# Go/no-go: run only `lb config` in the container. Fast (~1 min) sanity
# check that the Tails config tree still processes cleanly.
config:
    ./build.sh config

# Incremental rebuild: reuse the chroot/ from the last full build and
# re-run only the squashfs + ISO assembly (~10 min). Use after editing
# overlay files. Run a full `just build` first to create the chroot/.
sync-chroot:
    scripts/sync-runtime-to-chroot.sh

binary: elizaos-app sync-chroot
    ./build.sh binary

# Low-CPU incremental rebuild. Still requires an existing chroot/ from a
# previous full build.
binary-cool: elizaos-app sync-chroot
    scripts/run-cool-build.sh binary

# --- Test -------------------------------------------------------------------

# Run CPU-light static checks only. No Docker, no QEMU, no ISO build.
static-smoke:
    scripts/static-smoke.sh

# Start the staged bundled runtime and prove the local API endpoints needed by
# the desktop onboarding shell return successfully. No Docker, no QEMU.
runtime-smoke:
    scripts/runtime-api-smoke.sh

# Boot the built chroot/ in systemd-nspawn — seconds, no QEMU. For
# non-GUI sanity (do my systemd units parse? did my file land where I
# expect? does a service start?). --volatile=overlay keeps the chroot
# read-only; all writes go to a discarded tmpfs. NOT for GUI testing —
# use `just boot` for that.
nspawn:
    @test -d tails/chroot || { echo "no tails/chroot/ — run 'just build' first"; exit 1; }
    sudo systemd-nspawn --boot --directory=tails/chroot \
        --volatile=overlay --machine=elizaos-live-dev

# Boot the latest built ISO in QEMU. Tails stopped making the ISO
# isohybrid, so the ISO must be presented as a CD-ROM. Needs KVM.
boot:
    scripts/boot-qemu.sh

# Write the latest built USB image to a removable USB device with guard rails.
usb-write device:
    scripts/usb-write.sh "{{device}}"

# Write a specific ISO/USB image to a removable USB device with guard rails.
# ISO input is rejected by default unless a neighboring .img exists or
# ELIZAOS_CREATE_USB_IMAGE_FROM_ISO=1 is set.
usb-write-iso device iso:
    scripts/usb-write.sh "{{device}}" "{{iso}}"

# CPU-light validation for the active elizaOS Live distro.
lint:
    ./scripts/static-smoke.sh

test:
    ./scripts/static-smoke.sh

# --- CI aliases -------------------------------------------------------------
#
# The release pipeline (.github/workflows/build-linux-iso.yml) calls a fixed
# set of `iso-*` targets so a single contract spans every architecture. These are
# thin wrappers around the recipes above. If a step has no underlying
# implementation in this distro it prints a clear "skipping" message and
# exits 0 — the workflow gates the corresponding step explicitly so we never
# silently pretend a missing step ran.

# Stage build inputs (elizaOS app rsync into chroot_local-includes).
iso-stage: elizaos-app
    @echo "iso-stage: staged elizaOS app into chroot_local-includes overlay"

# Pre-cache the bundled LLM model. The elizaOS distro currently downloads the
# model from the chroot during live-build, so this compatibility target keeps
# the workflow uniform while doing no separate cache work.
iso-cache-model:
    @echo "iso-cache-model: skipping — elizaos resolves the model inside live-build's chroot; no separate cache step"

# Pre-cache the eliza CLI binary. Same story — fetched inside live-build.
iso-cache-cli:
    @echo "iso-cache-cli: skipping — elizaos resolves the eliza CLI inside live-build's chroot; no separate cache step"

# Alias for `config` (live-build go/no-go).
iso-config: config

# Alias for `static-smoke` (pre-build sanity check).
iso-check: static-smoke

# Alias for the full ISO build.
iso-build: build

# --- Clean ------------------------------------------------------------------

# Remove build artifacts (out/ + the chroot/binary/cache trees inside
# tails/). These are root-owned (built in-container as root), so this
# needs sudo. The apt-cacher-ng volume is left alone — see cache-clean.
clean:
    sudo rm -rf out tails/chroot tails/binary tails/cache tails/.build \
        tails/tmp tails/chroot.files tails/*.buildlog tails/*.iso tails/*.img
    @echo "Build artifacts cleared. The apt-cacher-ng cache volume is kept (run 'just cache-clean' to drop it)."

# Drop the persistent apt-cacher-ng package cache. Next build re-downloads
# everything from the network — only do this to reclaim disk or force a
# fully cold rebuild.
cache-clean:
    docker volume rm elizaos-acng elizaos-acng 2>/dev/null || true
    @echo "apt-cacher-ng cache volume removed."

# Fast per-arch smoke. First validates the per-arch bootloader package
# case in a tiny Docker build, then runs the real build.sh config path so
# ELIZAOS_ARCH plumbing reaches live-build's architecture/kernel settings.
# Use this when iterating on the multi-arch parameterization in
# Dockerfile / build.sh — catches "Unable to locate package grub-pc-bin"
# on arm64 / riscv64 in minutes instead of waiting hours for a real ISO
# build to fail at the same step.
#
#   just smoke-arch                 # default amd64, no emulation, ~2 min
#   just smoke-arch arm64           # qemu-aarch64 emulation, ~3 min
#   just smoke-arch riscv64         # qemu-riscv64 emulation, ~5 min
#
# arm64 + riscv64 require qemu-user-static + binfmt_misc registered:
#   docker run --rm --privileged tonistiigi/binfmt --install all
#
# Update the inline Dockerfile here whenever the real Dockerfile's
# per-arch package case changes.
smoke-arch arch="amd64":
    #!/usr/bin/env bash
    set -euo pipefail
    case "{{arch}}" in
        amd64|arm64|riscv64) ;;
        *) echo "ERROR: arch must be amd64, arm64, or riscv64 (got {{arch}})" >&2; exit 1 ;;
    esac
    tmp=$(mktemp -d)
    trap 'rm -rf "$tmp"' EXIT
    cat > "$tmp/Dockerfile" <<'EOF'
    FROM debian:trixie
    ENV DEBIAN_FRONTEND=noninteractive
    ARG TARGETARCH=amd64
    RUN apt-get update && apt-get install -y --no-install-recommends \
            grub2-common debootstrap rsync xorriso ca-certificates \
        && rm -rf /var/lib/apt/lists/*
    RUN apt-get update && case "${TARGETARCH}" in \
            amd64) apt-get install -y --no-install-recommends \
                       isolinux syslinux syslinux-common \
                       grub-pc-bin grub-efi-amd64-bin ;; \
            arm64) apt-get install -y --no-install-recommends \
                       grub-efi-arm64-bin ;; \
            riscv64) apt-get install -y --no-install-recommends \
                       grub-efi-riscv64-bin ;; \
        esac \
        && rm -rf /var/lib/apt/lists/*
    RUN echo "=== installed bootloader for TARGETARCH=${TARGETARCH} ===" \
        && dpkg-query -W -f '${Package}\n' \
            isolinux syslinux syslinux-common grub-pc-bin grub-efi-amd64-bin \
            grub-efi-arm64-bin grub-efi-riscv64-bin 2>/dev/null | sort
    EOF
    echo "=== smoke build {{arch}} (per-arch package set only, not the full ISO build) ==="
    docker build \
        --platform "linux/{{arch}}" \
        --build-arg "TARGETARCH={{arch}}" \
        -t "elizaos-smoke-{{arch}}" \
        "$tmp"
    echo
    echo "=== config smoke {{arch}} (real build.sh/live-build path) ==="
    ELIZAOS_ARCH="{{arch}}" ./build.sh config
    echo
    echo "✅ smoke passed for {{arch}}. To run the full ISO build:"
    echo "    ELIZAOS_ARCH={{arch}} just build"
