SHELL := /bin/bash
.PHONY: build build-debug image image-debug kernel kernel-clean proton-image proton-push proton-clean clean help

CONTAINER_RT := $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)
ifeq ($(CONTAINER_RT),)
$(error "Neither docker nor podman found in PATH")
endif

# The Containerfile COPYs from apps/ and crates/, so the build context is the
# repo root, not this directory — same reason borealis's build/ *is* its own
# context: here the guest source lives one level up instead of inside build/.
CONTEXT      := ..
# `ghcr.io/nestrilabs/nestri/base` (a package name with a `/` in it, matching
# how every other image this org has published is named) is a deliberate
# name, not just a tag: this is the image other builds start FROM —
# nesbox's jailer image extracts Mesa/virgl from it to keep the guest and
# host sides of the virtio-gpu native-context protocol on the same patched
# Mesa, and closed downstream builds layer Proton/Steam on top of it
# elsewhere. `runtime_prod`/`runtime_debug` already are exactly that: distro
# packages plus our own Mesa/nestri artifacts overlaid on top, nothing
# stripped that a downstream COPY --from= would miss — no separate
# unstripped tag is needed for this.
#
# The registry host is part of the name on purpose, including for local
# builds: a bare `nestrilabs/nestri` has no host, so anyone who pulls
# instead of building locally resolves it against Docker Hub by default,
# where it does not exist. One name, always pullable, beats a local-only
# short name and a different published one.
IMAGE_NAME   := ghcr.io/nestrilabs/nestri/base

# Proton is the one artifact worth publishing on its own: it takes hours to
# build and changes only when its tag moves, so the guest build pulls it by
# name instead of rebuilding it.
#
# `PROTON_TAG` is the single place to change it. The published version is
# derived from the tag rather than written twice, because the two are the same
# number in two spellings and an image whose name does not say which Proton is
# inside it is worse than no image at all. Changing the tag by hand and
# forgetting the version is exactly the mistake this removes.
PROTON_GIT     ?= https://github.com/gloriouseggroll/proton-ge-custom.git
PROTON_TAG     ?= GE-Proton11-7
PROTON_VERSION := 11.0-20260703
PROTON_IMAGE   ?= ghcr.io/nestrilabs/proton-cachyos-native-wow64
PROTON_REF     := $(PROTON_IMAGE):$(PROTON_VERSION)
# The directory name under compatibilitytools.d, and the name Steam shows.
PROTON_NAME    := proton-ge
OUTPUT_DIR   := output
# Source, build tree, ccache and cargo downloads. Tens of gigabytes, and it
# lives under output/ because that directory is already ignored by git and
# kept out of the guest build's context.
PROTON_WORK  := $(OUTPUT_DIR)/proton
ROOTFS_SIZE  ?= 3G
FORCE_REBUILD ?=

# The guest kernel: CachyOS's fork, for its scheduler patches, with our own
# minimal config on top (kernel/nestri.fragment). The tree is a clone rather
# than vendored, and lives under output/ for the same reason Proton's does.
# KERNEL_SRC may point at an existing tree elsewhere.
KERNEL_GIT    ?= https://github.com/CachyOS/linux.git
KERNEL_REF    ?= cachyos-7.2.6-1
# The target hardware is at least AVX2-capable. Never `native`: the build host
# is not the target. Empty leaves the tree's own generic choice alone.
KERNEL_MARCH  ?= x86-64-v3

# Experimental: KERNEL_INFINITY=1 applies the Infinity scheduler series (fair,
# rt and DRM scheduler rework) on top. It builds in its own tree and installs
# under its own name, so the stock kernel is never patched and switching the
# flag back and forth never needs a revert.
#
# The series is published per CachyOS release, so its directory is derived
# from KERNEL_REF rather than written twice: bumping the kernel to a release
# the series does not cover fails at the patch step, not in a booted box.
KERNEL_INFINITY ?=
INFINITY_GIT    ?= https://github.com/galpt/infinity-sched-new.git
INFINITY_REV    ?= e6c85d841f25e21393a7ea47fdef286318915d8c
INFINITY_SERIES := patches/cachyos/tuned-eevdf/$(KERNEL_REF:cachyos-%=%)
INFINITY_WORK   := $(OUTPUT_DIR)/infinity-sched

# The guest half of NVIDIA forwarding, built into the kernel. A branch, so each
# kernel build takes the driver as it is now; set NVGPU_REF to a tag or commit
# to pin it. The commit built is recorded as $(KERNEL_OUTPUT).nvgpu-rev.
NVGPU_GIT  ?= https://github.com/nestrilabs/virtio-nvgpu.git
NVGPU_REF  ?= dev
NVGPU_WORK := $(OUTPUT_DIR)/virtio-nvgpu

ifneq ($(KERNEL_INFINITY),)
KERNEL_SRC    ?= $(OUTPUT_DIR)/kernel-infinity
KERNEL_OUTPUT := $(OUTPUT_DIR)/vmlinux-infinity
else
KERNEL_SRC    ?= $(OUTPUT_DIR)/kernel
KERNEL_OUTPUT := $(OUTPUT_DIR)/vmlinux
endif

build:
	DOCKER_BUILDKIT=1 $(CONTAINER_RT) build $(if $(FORCE_REBUILD),--no-cache,) \
		--build-arg PROTON_IMAGE=$(PROTON_REF) \
		-f Containerfile -t $(IMAGE_NAME):latest --target runtime_prod $(CONTEXT)

build-debug:
	DOCKER_BUILDKIT=1 $(CONTAINER_RT) build $(if $(FORCE_REBUILD),--no-cache,) \
		--build-arg PROTON_IMAGE=$(PROTON_REF) \
		-f Containerfile -t $(IMAGE_NAME):debug --target runtime_debug $(CONTEXT)

# Hours, and only when PROTON_TAG moves. Two steps: the build runs on the host,
# because proton-ge's build drives the container engine itself, and the
# finished tree is then the whole context of a FROM scratch image. See the head
# of Containerfile.proton for why the build cannot run inside it.
#
# A second run of the same tag resumes where the last one stopped. A new tag,
# or FORCE_REBUILD, starts the tree over but keeps ccache.
proton-image:
	PROTON_GIT=$(PROTON_GIT) PROTON_TAG=$(PROTON_TAG) PROTON_WORK=$(PROTON_WORK) \
		BUILD_NAME=$(PROTON_NAME) CONTAINER_ENGINE=$(notdir $(CONTAINER_RT)) \
		FORCE_REBUILD=$(FORCE_REBUILD) \
		bash scripts/proton-build.sh
	$(CONTAINER_RT) build --build-arg BUILD_NAME=$(PROTON_NAME) \
		-f Containerfile.proton -t $(PROTON_REF) $(PROTON_WORK)/obj/dist
	@echo "Built $(PROTON_REF)"

# Publishing is what makes `make build` cheap for everyone else, since that
# build pulls this by name. Push before expecting anyone to use a new tag.
proton-push: proton-image
	$(CONTAINER_RT) push $(PROTON_REF)

# Not `sudo make image`/sudo'd in here: mkimage.sh runs as you and escalates
# only the specific commands that need root. Under rootless Podman, `make
# build` stores the image in *your* storage — sudo-ing the whole script would
# have root's podman look for that tag in its own, separate storage and fail
# to find it.
image: build
	@mkdir -p $(OUTPUT_DIR)
	bash scripts/mkimage.sh $(IMAGE_NAME):latest $(OUTPUT_DIR)/rootfs.ext4 $(ROOTFS_SIZE)

image-debug: build-debug
	@mkdir -p $(OUTPUT_DIR)
	bash scripts/mkimage.sh $(IMAGE_NAME):debug $(OUTPUT_DIR)/rootfs-debug.ext4 $(ROOTFS_SIZE)

# Not part of `build`: it changes far less often than the rootfs, and nothing
# in the image depends on it. A rerun rebuilds only what changed in the tree.
kernel:
	KERNEL_GIT=$(KERNEL_GIT) KERNEL_REF=$(KERNEL_REF) KERNEL_SRC=$(KERNEL_SRC) \
		KERNEL_MARCH=$(KERNEL_MARCH) KERNEL_OUTPUT=$(KERNEL_OUTPUT) \
		KERNEL_INFINITY=$(KERNEL_INFINITY) INFINITY_GIT=$(INFINITY_GIT) \
		INFINITY_REV=$(INFINITY_REV) INFINITY_SERIES=$(INFINITY_SERIES) \
		INFINITY_WORK=$(INFINITY_WORK) \
		NVGPU_GIT=$(NVGPU_GIT) NVGPU_REF=$(NVGPU_REF) NVGPU_WORK=$(NVGPU_WORK) \
		bash scripts/kernel-build.sh

# Leaves $(PROTON_WORK) and the kernel alone: one is hours of build and ccache,
# the other a clone and a warm object tree, and a rootfs is neither.
# proton-clean and kernel-clean are the ones that drop them.
clean:
	find $(OUTPUT_DIR) -mindepth 1 -maxdepth 1 ! -name proton ! -name 'kernel*' \
		! -name 'vmlinux*' ! -name infinity-sched ! -name virtio-nvgpu -exec rm -rf {} + 2>/dev/null || true

kernel-clean:
	rm -rf $(OUTPUT_DIR)/kernel $(OUTPUT_DIR)/kernel-infinity $(INFINITY_WORK) $(NVGPU_WORK) \
		$(OUTPUT_DIR)/vmlinux $(OUTPUT_DIR)/vmlinux-infinity $(OUTPUT_DIR)/vmlinux*.nvgpu-rev

proton-clean:
	rm -rf $(PROTON_WORK)

help:
	@echo "Usage:"
	@echo "  make build              Build the runtime_prod container image"
	@echo "  make build-debug        Build the runtime_debug container image"
	@echo "  make image              Build + pack runtime_prod into output/rootfs.ext4"
	@echo "  make image-debug        Build + pack runtime_debug into output/rootfs-debug.ext4"
	@echo "  make kernel              Build the guest kernel into output/vmlinux"
	@echo "  make KERNEL_INFINITY=1 kernel  Experimental Infinity scheduler build → output/vmlinux-infinity"
	@echo "  make kernel-clean        Remove both kernel trees and images"
	@echo "  make clean               Remove the rootfs images from output/"
	@echo "  make proton-image        Build Proton from source (hours)"
	@echo "  make proton-clean        Remove the Proton source, build tree and ccache"
	@echo "  make proton-push         Build it and publish it"
	@echo "  make FORCE_REBUILD=1 ...  Rebuild from scratch, no layer cache"
	@echo "  make PROTON_TAG=... ...   Use a different proton-ge tag"
	@echo "  make ROOTFS_SIZE=8G image  Override the packed image size (default 5G)"
