# DCENTos Hacker Shell - Top-level Makefile
# D-Central Technologies, 2026
#
# This wraps Buildroot with our BR2_EXTERNAL configuration.
# Buildroot must be cloned as a submodule or symlinked at ./buildroot/
#
# Quick start:
#   make setup          # Clone Buildroot + apply defconfig
#   make                # Build everything
#   make menuconfig     # Modify Buildroot config
#   make busybox-menuconfig  # Modify BusyBox config
#

BUILDROOT_DIR := $(CURDIR)/buildroot
BR2_EXTERNAL := $(CURDIR)/br2_external_dcentos
DEFCONFIG := dcentos_s9_defconfig
# Defconfig fragments to concatenate BEFORE $(DEFCONFIG). The concatenation
# is materialized as $(DEFCONFIG)_full inside br2_external_dcentos/configs/
# by the merge-defconfig target. Order matters: workspace-wide common first,
# arch-specific second (if any), per-product defconfig last so its overrides
# win. For non-S9 targets, override DEFCONFIG and DEFCONFIG_FRAGMENTS on the
# make command line, e.g.:
#   make DEFCONFIG=dcentos_am3_s21_defconfig \
#        DEFCONFIG_FRAGMENTS="dcentos-common.fragment dcentos_am3_aml_common.fragment" \
#        setup
# build_in_docker.sh maintains its own fragment table; this Makefile is
# purely a developer convenience.
DEFCONFIG_FRAGMENTS := dcentos-common.fragment
DEFCONFIG_FULL := $(patsubst %_defconfig,%_full_defconfig,$(DEFCONFIG))
NPROC := $(shell nproc 2>/dev/null || echo 4)

# Buildroot version: pin to specific commit for reproducibility
# Using same commit as mujina-xilinx-platform (Nov 2025, post-2025.08.2)
BUILDROOT_REPO := https://github.com/buildroot/buildroot.git
BUILDROOT_COMMIT := 7c8edc1b402efcd7bba2dabfe0b3be877adaed7a

.PHONY: all setup defconfig merge-defconfig menuconfig busybox-menuconfig clean distclean help sd-image sd-image-standalone sd-image-piggyback sd-disk-image sd-disk-image-piggyback am3-bb-sd-disk-image flash-braiinsos release dev test test-host test-api test-waves test-clippy-input test-dashboard test-python-safety verify install-hooks

all:
	@if [ ! -d "$(BUILDROOT_DIR)" ]; then \
		echo "ERROR: Buildroot not found. Run 'make setup' first."; \
		exit 1; \
	fi
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) -j$(NPROC)

# Materialize a merged $(DEFCONFIG_FULL) from fragments + per-product defconfig.
# Fragments are concatenated first, per-product defconfig last (last write wins).
# The output file is git-ignored (it is fully derived) and lives in the same
# configs/ dir so Buildroot's `make defconfig` can pick it up by basename.
merge-defconfig:
	@CONFIGS_DIR="$(BR2_EXTERNAL)/configs"; \
	OUT="$$CONFIGS_DIR/$(DEFCONFIG_FULL)"; \
	echo "[merge-defconfig] $(DEFCONFIG_FRAGMENTS) + $(DEFCONFIG) -> $(DEFCONFIG_FULL)"; \
	{ \
		echo "# === GENERATED by Makefile target merge-defconfig — DO NOT EDIT ==="; \
		echo "# Source: $(DEFCONFIG_FRAGMENTS) $(DEFCONFIG)"; \
		echo "# Edit the upstream fragments / defconfig instead."; \
		echo "# ================================================================="; \
		for frag in $(DEFCONFIG_FRAGMENTS); do \
			FRAG_PATH="$$CONFIGS_DIR/$$frag"; \
			[ -f "$$FRAG_PATH" ] || { echo "ERROR: fragment missing: $$FRAG_PATH" >&2; exit 1; }; \
			echo ""; \
			echo "# --- begin fragment: $$frag ---"; \
			cat "$$FRAG_PATH"; \
			echo "# --- end fragment: $$frag ---"; \
		done; \
		BASE_PATH="$$CONFIGS_DIR/$(DEFCONFIG)"; \
		[ -f "$$BASE_PATH" ] || { echo "ERROR: defconfig missing: $$BASE_PATH" >&2; exit 1; }; \
		echo ""; \
		echo "# --- begin per-product defconfig: $(DEFCONFIG) ---"; \
		cat "$$BASE_PATH"; \
		echo "# --- end per-product defconfig: $(DEFCONFIG) ---"; \
	} > "$$OUT"; \
	echo "[merge-defconfig] wrote $$OUT ($$(wc -l < $$OUT) lines)"

setup: merge-defconfig
	@echo "=== DCENTos Build Environment Setup ==="
	@if [ ! -d "$(BUILDROOT_DIR)" ]; then \
		echo "Cloning Buildroot..."; \
		git clone $(BUILDROOT_REPO) $(BUILDROOT_DIR); \
		cd $(BUILDROOT_DIR) && git checkout $(BUILDROOT_COMMIT); \
		echo "Buildroot cloned and pinned to $(BUILDROOT_COMMIT)"; \
	else \
		echo "Buildroot already present."; \
	fi
	@echo "Applying merged defconfig $(DEFCONFIG_FULL)..."
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) $(DEFCONFIG_FULL)
	@echo ""
	@echo "=== Setup Complete ==="
	@echo "Run 'make' to build the firmware image."
	@echo "Output: buildroot/output/images/ramdisk.itb"

defconfig: merge-defconfig
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) $(DEFCONFIG_FULL)

menuconfig:
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) menuconfig

busybox-menuconfig:
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) busybox-menuconfig

savedefconfig:
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) savedefconfig \
		BR2_DEFCONFIG=$(BR2_EXTERNAL)/configs/$(DEFCONFIG)

# Rebuild just our tools package (fast iteration)
tools-rebuild:
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) dcentos-tools-rebuild

# Build standalone SD card image (current primary S9 path)
# Requires: extractions/s9/ populated, rootfs.ext2.gz built
sd-image:
	@$(CURDIR)/scripts/build_sd_image.sh --standalone

# Explicit standalone alias (includes BOOT.BIN)
sd-image-standalone:
	@$(CURDIR)/scripts/build_sd_image.sh --standalone

# Legacy compatibility path: piggyback on resident BraiinsOS U-Boot/runtime handoff
sd-image-piggyback:
	@$(CURDIR)/scripts/build_sd_image.sh --piggyback

# Build standalone SD card disk image (flashable with dd/balenaEtcher)
sd-disk-image:
	@$(CURDIR)/scripts/build_sd_image.sh --standalone --disk-image

# Legacy compatibility disk image (explicit piggyback mode)
sd-disk-image-piggyback:
	@$(CURDIR)/scripts/build_sd_image.sh --piggyback --disk-image

# Build a flashable .img for the am3-bb-s19jpro (AM335x BeagleBone control board).
# Parallel to S9's `sd-disk-image` but produces an AM335x boot layout (FAT32
# at sector 2048, MLO + u-boot.img + uImage + DTB + ramdisk.gz).
# Operator must supply the AM335x boot artifacts via AM3_BB_ARTIFACTS=<dir>.
# Rootfs comes from Buildroot output or AM3_BB_PAYLOAD_TAR=<payload.tar>.
# Usage:
#   make am3-bb-sd-disk-image AM3_BB_PAYLOAD_TAR=output/dcentos-am3-bb-s19jpro-sdcard.tar AM3_BB_ARTIFACTS=/path/to/boot-artifacts
#   make am3-bb-sd-disk-image AM3_BB_SIZE_MB=192 AM3_BB_PAYLOAD_TAR=... AM3_BB_ARTIFACTS=...
#   make am3-bb-sd-disk-image AM3_BB_ALLOW_INCOMPLETE=1   # nonbootable lab artifact
am3-bb-sd-disk-image:
	@set -- "$(CURDIR)/scripts/build_am3_bb_sd_disk_image.sh"; \
	if [ -n "$(AM3_BB_PAYLOAD_TAR)" ]; then set -- "$$@" --payload-tar "$(AM3_BB_PAYLOAD_TAR)"; fi; \
	if [ -n "$(AM3_BB_PAYLOAD_DIR)" ]; then set -- "$$@" --payload-dir "$(AM3_BB_PAYLOAD_DIR)"; fi; \
	if [ -n "$(AM3_BB_ARTIFACTS)" ]; then set -- "$$@" --artifacts "$(AM3_BB_ARTIFACTS)"; fi; \
	if [ -n "$(AM3_BB_SIZE_MB)" ]; then set -- "$$@" --size-mb "$(AM3_BB_SIZE_MB)"; fi; \
	if [ "$(AM3_BB_ALLOW_INCOMPLETE)" = "1" ]; then set -- "$$@" --allow-incomplete; fi; \
	"$$@"

# Quick deploy: cross-compile dcentrald and SCP to miner (no Buildroot rebuild)
# Usage: make deploy MINER_IP=192.168.101.97
deploy:
	@if [ -z "$(MINER_IP)" ]; then \
		echo "Usage: make deploy MINER_IP=192.168.101.97"; \
		exit 1; \
	fi
	@$(CURDIR)/scripts/deploy_dcentrald.sh $(MINER_IP)

# Passthrough is a config knob ([mining] passthrough = true), not a CLI flag.
# The --passthrough flag was removed in Wave B (2026-05-19, G-T8-1).
deploy-passthrough:
	@echo "Passthrough is no longer a CLI flag (removed Wave B, 2026-05-19)."
	@echo "Set [mining] passthrough = true in the miner's /data/dcentrald.toml, or deploy a"
	@echo "passthrough config:  scripts/dev_deploy.sh $(MINER_IP) --config <passthrough.toml> --verify"
	@exit 1

# Flash to BraiinsOS S9 via UBI (legacy compatibility path)
# Usage: make flash-braiinsos MINER_IP=192.168.101.97
flash-braiinsos:
	@if [ -z "$(MINER_IP)" ]; then \
		echo "Usage: make flash-braiinsos MINER_IP=192.168.101.97"; \
		exit 1; \
	fi
	@$(CURDIR)/scripts/flash_braiinsos.sh $(MINER_IP) $(BUILDROOT_DIR)/output/images/rootfs.squashfs

# W1.2 (2026-05-07): production-build entry point. Fails closed when the
# manifest pubkey pin is not exported. The first immutable-source capsule lane
# is intentionally S9-only; other targets remain blocked until their Cargo and
# boot-input paths use the same invocation boundary.
#
# `make release` ALWAYS builds a hardened RELEASE image (DCENT_RELEASE_IMAGE=1):
# root login is locked ("*", no shared default password), the MCP control
# server requires a bearer token for write/control tools and fails closed when
# none is provisioned, and the image is stamped /etc/dcentos/release-image. The
# historical unhardened DEV image posture (root:dcentral, open MCP) is not
# currently packageable: its mutable direct-build lane is intentionally blocked
# until a separate lab capsule provides equivalent ownership and isolation.
RELEASE_TARGET ?= s9
.PHONY: beta beta-plan
DROP ?= 1
MINIMUM_VERSION ?=
CHANNEL_BASE_URL ?=
# Explicit make parameters become explicit signed build selections. Shell
# expansion preserves their literal values; train validates before setup.
beta: export MINIMUM_VERSION := $(MINIMUM_VERSION)
beta: export CHANNEL_BASE_URL := $(CHANNEL_BASE_URL)
beta:
	@python3 $(CURDIR)/scripts/release_train.py --run --drop $(DROP) --repro --minimum-version "$$MINIMUM_VERSION" --channel-base-url "$$CHANNEL_BASE_URL"

beta-plan:
	@python3 $(CURDIR)/scripts/release_train.py --plan --drop $(DROP)

release:
	@if [ -z "$$DCENT_MANIFEST_PUBLIC_KEY_HEX" ]; then \
		echo "ERROR: 'make release' requires DCENT_MANIFEST_PUBLIC_KEY_HEX (64 hex chars)."; \
		echo "       export DCENT_MANIFEST_PUBLIC_KEY_HEX=<hex64> first."; \
		echo "       The legacy direct lab-image lane is disabled until it has its own capsule."; \
		exit 1; \
	fi
	@if [ -z "$$DCENT_RELEASE_SIGNING_KEY" ]; then \
		echo "ERROR: 'make release' requires DCENT_RELEASE_SIGNING_KEY for sysupgrade signing."; \
		echo "       Generate with scripts/generate_release_keypair.sh."; \
		exit 1; \
	fi
	@echo "release: running local verification gate (make verify) before build..."
	@$(MAKE) verify
	@bash $(CURDIR)/scripts/build_release_capsule.sh --producer $(RELEASE_TARGET) --channel beta --output-root $(CURDIR)/output --result-json $(CURDIR)/output/$(RELEASE_TARGET)-producer-result.json

# A direct lab package used to reuse mutable checkout, Cargo, and Buildroot
# state. Binary receipt v4 intentionally rejects that weaker boundary. Keep the
# public target fail-closed until a separate lab capsule owns the same source,
# result, and cleanup lifecycles; contributors can still build/test dcentrald.
DEV_TARGET ?= s9
dev:
	@echo "ERROR: make dev image packaging is temporarily unsupported." >&2
	@echo "       Direct packaging cannot satisfy the invocation-bound binary receipt contract." >&2
	@echo "       Use 'make test' for contributor builds; do not weaken the release capsule." >&2
	@echo "       A future lab capsule must be explicit, isolated, and permanently marked non-release." >&2
	@exit 2

# Wave-A / SB-2 (2026-05-28): the workspace TEST gate that was MISSING. `make
# release`/build_in_docker.sh run only `cargo build --release` (no tests), and
# the .github workflows never fire (empty git remote) — so non-compiling tests
# (e.g. SB-3's broken include_str! path) shipped silently. By DEFAULT this
# compile-gates every dcentrald workspace test for the armv7 musl target in
# Docker (the full-repo mount that resolves the include_str! sibling paths).
# Pass RUN_TESTS=1 to also EXECUTE tests on the container host target.
#   make test                    # compile-gate all workspace tests (Docker)
#   make test RUN_TESTS=1        # compile + run (host x86_64 in Docker)
#   make test TEST_PACKAGE=dcentrald
test:
	@set -- "$(CURDIR)/scripts/run_dcentrald_tests.sh"; \
	if [ "$(RUN_TESTS)" = "1" ]; then set -- "$$@" --run; fi; \
	if [ -n "$(TEST_PACKAGE)" ]; then set -- "$$@" --package "$(TEST_PACKAGE)"; fi; \
	bash "$$@"

# Bug-hunt follow-up (2026-05-28): RUN (not just compile-gate) the no-HAL,
# host-testable crate tests directly on the dev host. `make test` compile-gates
# the target ABI by default; `RUN_TESTS=1` uses the wrapper's host-test backend.
# Keep this smaller native loop as a fast contributor signal.
# These crates execute on the Linux/WSL host, so they ARE a fast, real
# verification signal. No Docker, no hardware.
HOST_TEST_CRATES := dcentrald-api-types dcentrald-common dcentrald-stratum dcentrald-silicon-profiles dcentrald-asic
test-host:
	@cd "$(CURDIR)/dcentrald" && for c in $(HOST_TEST_CRATES); do \
		echo "[test-host] cargo test -p $$c"; \
		rustup run 1.90.0 cargo test --locked -p $$c --quiet || { echo "[test-host] FAILED: $$c"; exit 1; }; \
	done; \
	echo "[test-host] cargo test -p dcentrald-thermal --no-default-features"; \
	rustup run 1.90.0 cargo test --locked -p dcentrald-thermal --no-default-features --quiet || { echo "[test-host] FAILED: dcentrald-thermal"; exit 1; }; \
	echo "[test-host] all host-testable crates GREEN"

test-api:
	@cd "$(CURDIR)/dcentrald" && \
	echo "[test-api] cargo test -p dcentrald-api --tests"; \
	rustup run 1.90.0 cargo test --locked -p dcentrald-api --tests --quiet || { echo "[test-api] FAILED: dcentrald-api"; exit 1; }; \
	echo "[test-api] cargo test -p dcentrald --bin dcentrald"; \
	rustup run 1.90.0 cargo test --locked -p dcentrald --bin dcentrald --quiet || { echo "[test-api] FAILED: dcentrald"; exit 1; }; \
	echo "[test-api] API and daemon tests GREEN"

test-waves:
	@bash "$(CURDIR)/scripts/run_wave_regressions.sh"

test-clippy-input:
	@cd "$(CURDIR)/dcentrald" && \
	echo "[test-clippy-input] dcentrald-stratum unwrap/expect/panic deny"; \
	rustup run 1.90.0 cargo clippy --no-deps -p dcentrald-stratum --lib -- -D clippy::unwrap_used -D clippy::expect_used -D clippy::panic && \
	echo "[test-clippy-input] dcentrald-asic unwrap/expect/panic deny"; \
	rustup run 1.90.0 cargo clippy --no-deps -p dcentrald-asic --lib -- -D clippy::unwrap_used -D clippy::expect_used -D clippy::panic

test-dashboard:
	@command -v npm >/dev/null 2>&1 || { echo "ERROR: npm is required for test-dashboard" >&2; exit 1; }
	@cd "$(CURDIR)/dashboard" && npm run build
	@cd "$(CURDIR)/dashboard" && npm test

test-python-safety:
	@PYTHON_BIN="$$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)"; \
	if [ -z "$$PYTHON_BIN" ]; then \
		echo "ERROR: python3 or python is required for test-python-safety" >&2; \
		exit 1; \
	fi; \
	"$$PYTHON_BIN" "$(CURDIR)/scripts/test_python_safety_gate_wiring.py" && \
	"$$PYTHON_BIN" "$(CURDIR)/scripts/run_python_safety_tests.py"

# Full offline verification loop: static offline gates + workspace test
# compile-gate. This is what a CI runner / pre-push hook SHOULD run. The repo
# has no git remote today, so the committed .github workflows never fire (SB-2);
# `make verify` is the local stand-in until a remote/hook is wired. No hardware.
verify:
	@bash "$(CURDIR)/scripts/ci_offline_gates.sh"
	@$(MAKE) test-python-safety
	@$(MAKE) test-dashboard
	@$(MAKE) test-host
	@$(MAKE) test-api
	@$(MAKE) test-waves
	@$(MAKE) test-clippy-input
	@$(MAKE) test

# Wave-A / SB-2 (2026-05-28): install local hooks that run the fast static
# gates before commit and `make verify` before publication. The repo has no
# remote, so the committed workflows never fire. DCENT_SKIP_VERIFY=1 is a
# hook-only emergency bypass for a local WIP commit/push; `make release` still
# runs `make verify` and does not honor that bypass.
install-hooks:
	@sh "$(CURDIR)/scripts/install_git_hooks.sh"

clean:
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) clean

distclean:
	$(MAKE) -C $(BUILDROOT_DIR) BR2_EXTERNAL=$(BR2_EXTERNAL) distclean

help:
	@echo "DCENTos Hacker Shell Build System"
	@echo ""
	@echo "Targets:"
	@echo "  setup              - Clone Buildroot + apply defconfig (run first!)"
	@echo "  all (default)      - Build everything"
	@echo "  menuconfig         - Configure Buildroot packages"
	@echo "  busybox-menuconfig - Configure BusyBox applets"
	@echo "  savedefconfig      - Save current config to defconfig"
	@echo "  tools-rebuild      - Rebuild only DCENTos tools (fast)"
	@echo "  sd-image           - Build standalone SD boot files (primary S9 path)"
	@echo "  sd-image-standalone - Explicit standalone SD boot alias"
	@echo "  sd-image-piggyback - Build legacy BraiinsOS piggyback SD files"
	@echo "  sd-disk-image      - Build standalone flashable .img file for balenaEtcher/dd"
	@echo "  sd-disk-image-piggyback - Build legacy piggyback .img file"
	@echo "  am3-bb-sd-disk-image - Build flashable .img for am3-bb-s19jpro [AM3_BB_PAYLOAD_TAR=... AM3_BB_ARTIFACTS=...]"
	@echo "  deploy             - Quick deploy dcentrald binary via SSH (MINER_IP=x.x.x.x)"
	@echo "  deploy-passthrough - (removed) set [mining] passthrough = true in /data/dcentrald.toml"
	@echo "  flash-braiinsos    - Legacy/lab BraiinsOS-rootfs compatibility path"
	@echo "  release            - Selected producer capsule (requires signing key +"
	@echo "                       manifest public-key pin; RELEASE_TARGET=s9)"
	@echo "  dev                - Image packaging disabled until a separate lab capsule exists"
	@echo "  test               - Compile-gate all dcentrald workspace tests in Docker"
	@echo "                       (RUN_TESTS=1 to execute; TEST_PACKAGE=<crate> to scope)"
	@echo "  test-host          - RUN the no-HAL host-testable crate tests on the host"
	@echo "                       (api-types/common/stratum/silicon-profiles/asic + thermal --no-default-features)"
	@echo "  test-api           - RUN dcentrald-api tests + dcentrald binary tests on the host"
	@echo "  test-waves         - RUN the wave48/54/55 host regression suite"
	@echo "  test-dashboard     - Build the dashboard and RUN its Vitest suite"
	@echo "  test-python-safety - RUN explicitly owned DCENTos Python safety suites"
	@echo "  verify             - Release gate: offline + Python/dashboard/Rust tests + compile-gate"
	@echo "  install-hooks      - Install local pre-commit/pre-push verification hooks"
	@echo "  clean              - Clean build artifacts"
	@echo "  distclean          - Full clean (removes downloads too)"
	@echo ""
	@echo "Output files (after build):"
	@echo "  buildroot/output/images/rootfs.squashfs   - Legacy/lab BraiinsOS UBI compatibility artifact"
	@echo "  buildroot/output/images/ramdisk.itb       - Legacy stock NAND research artifact"
	@echo "  buildroot/output/images/ramdisk.sig       - SHA256 for mtd3 patch"
	@echo "  buildroot/output/images/BUILD_INFO.txt"
	@echo ""
	@echo "Services (auto-start on boot):"
	@echo "  Port 22   — SSH (dropbear; DEV builds: root/dcentral, RELEASE builds: root locked)"
	@echo "  Port 80   — Web Dashboard (system status, mining stats)"
	@echo "  Port 3000 — MCP Server (AI assistant integration, JSON-RPC)"
	@echo "  Port 4028 — CGMiner-compatible API (dcentrald)"
	@echo "  Port 8080 — REST API + WebSocket (dcentrald)"
	@echo ""
	@echo "SD card files (after sd-image):"
	@echo "  buildroot/output/images/sd_card/fit.itb        - FIT image for SD"
	@echo "  buildroot/output/images/sd_card/system.bit.gz  - FPGA bitstream"
	@echo "  buildroot/output/images/sd_card/uEnv.txt       - Boot environment"
