DATE := $(shell date +%F)
TIME := $(shell date +%H:%M)
LOGROOT := $(abspath ../log)
LOGDIR := $(LOGROOT)/$(DATE)/$(TIME)

# bpf/ is at bpftests/bpf/, vm-credentials is written to bpftests/controller/
CREDENTIALS_FILE := ../controller/vm-credentials
VM_USER ?= $(shell sed -n 's/^VM_USER="\(.*\)"/\1/p' $(CREDENTIALS_FILE) 2>/dev/null)
MONITOR_PROFILE ?= $(shell sed -n 's/^MONITOR_PROFILE="\(.*\)"/\1/p' $(CREDENTIALS_FILE) 2>/dev/null)
MONITOR_PROFILE := $(or $(MONITOR_PROFILE),investigate)
MONITOR_HOOKS ?= $(shell sed -n 's/^MONITOR_HOOKS="\(.*\)"/\1/p' $(CREDENTIALS_FILE) 2>/dev/null)
MONITOR_HOOKS_ARG := $(if $(filter custom,$(MONITOR_PROFILE)),--hooks $(MONITOR_HOOKS),)
INCLUDE_SYSTEM_ARG := $(if $(or $(filter debug-system,$(MONITOR_PROFILE)),$(findstring system,$(MONITOR_HOOKS))),--include-system-events,)

VM_IP  := 192.168.100.10
VM_SSH := $(VM_USER)@$(VM_IP)
SSH_OPTS := -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=3
DEPLOY_DIR := /usr/local/libexec/.bpfpot
VSOCK_PORT := 12345
IGNORE_SOURCE_IP ?= $(shell sed -n 's/^IGNORE_SOURCE_IP="\(.*\)"/\1/p' $(CREDENTIALS_FILE) 2>/dev/null)
IGNORE_IP_ARG := $(if $(strip $(IGNORE_SOURCE_IP)),--ignore-ip $(strip $(IGNORE_SOURCE_IP)),)

# Guard macro — insert at the top of any recipe that requires VM_USER
define check_vm_user
	@if [ -z "$(VM_USER)" ]; then \
		echo "Error: VM_USER is not set. Run build.sh first to generate $(CREDENTIALS_FILE)"; \
		exit 1; \
	fi
endef

all: bpf ensure-loader

bpf: src/honeypot.skel.h

honeypot.bpf.o: vmlinux.h src/honeypot.bpf.c include/common.h
	clang -O2 -g -target bpf -D__TARGET_ARCH_x86 -c src/honeypot.bpf.c -o honeypot.bpf.o -I .

src/honeypot.skel.h: honeypot.bpf.o
	/sbin/bpftool gen skeleton honeypot.bpf.o name honeypot_bpf > src/honeypot.skel.h

loader: src/loader.c src/honeypot.skel.h include/common.h include/protocol.h
	clang -o loader src/loader.c -lbpf -lelf -pthread

ensure-loader: loader
	@test -f ./loader || { echo "Loader link step did not produce ./loader"; exit 1; }
	chmod 0700 ./loader

vmlinux.h:
	/sbin/bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

upload:
	$(call check_vm_user)
	cp include/data.h.template include/data.h
	tar czf - Makefile preflight.sh src/*.c include/*.h deploy/*.service helper/*.py tests/*.py | ssh $(SSH_OPTS) $(VM_SSH) \
		'sudo install -d -o root -g root -m 0700 $(DEPLOY_DIR) && \
		 sudo tar xzf - --no-same-owner -C $(DEPLOY_DIR) && \
		 sudo chown -R root:root $(DEPLOY_DIR) && \
		 sudo find $(DEPLOY_DIR) -type d -exec chmod 0700 {} \; && \
		 sudo find $(DEPLOY_DIR) -type f -exec chmod 0600 {} \; && \
		 if [ -f $(DEPLOY_DIR)/bpf/Makefile ] && [ -f $(DEPLOY_DIR)/bpf/src/honeypot.bpf.c ]; then \
		   find $(DEPLOY_DIR)/bpf -depth -delete; \
		 fi'

listen:
	mkdir -p "$(LOGDIR)"
	@touch "$(LOGDIR)/transport.jsonl"
	@if [ -e "$(LOGROOT)/current" ] && [ ! -L "$(LOGROOT)/current" ]; then \
	  mv "$(LOGROOT)/current" "$(LOGROOT)/current.legacy.$$(date +%s)"; \
	 fi
	ln -sfnT "$(LOGDIR)" "$(LOGROOT)/current"
	@echo "Logging current run to $(LOGDIR)"
	python3 -u ./listener.py --transport vsock --vsock-port $(VSOCK_PORT) --guest-cid 10 --debug 1 --log "$(LOGDIR)" --status-file "$(LOGROOT)/live-status.json" $(INCLUDE_SYSTEM_ARG)

go: upload
	$(call check_vm_user)
	ssh $(SSH_OPTS) $(VM_SSH) 'sudo -n make -C $(DEPLOY_DIR) go-on-pot VM_USER=$(VM_USER) VSOCK_PORT=$(VSOCK_PORT) IGNORE_SOURCE_IP=$(IGNORE_SOURCE_IP) MONITOR_PROFILE=$(MONITOR_PROFILE) MONITOR_HOOKS=$(MONITOR_HOOKS)'

go-on-pot:
	$(MAKE) clean
	$(MAKE) timesync
	$(MAKE) all
	$(MAKE) preflight
	$(MAKE) proc-hide
	$(MAKE) install-service

preflight: ensure-loader
	chmod 0700 ./preflight.sh
	./preflight.sh $(MONITOR_PROFILE) $(MONITOR_HOOKS)

test:
	python3 -m unittest discover -s tests -v

clean:
	rm -f loader honeypot.bpf.o src/honeypot.skel.h

timesync:
	@if command -v rdate >/dev/null 2>&1; then \
		rdate -n pool.ntp.org || echo "Warning: time synchronization failed; continuing with current guest clock"; \
	else \
		echo "Warning: rdate is unavailable; continuing with current guest clock"; \
	fi

load: all
	chmod 0700 ./loader
	@sudo ./loader --profile $(MONITOR_PROFILE) $(MONITOR_HOOKS_ARG) --vsock-port $(VSOCK_PORT) 2>&1; status=$$?; \
	if [ $$status -eq 0 ]; then \
		echo "Loader exited cleanly."; \
		exit 0; \
	else \
		echo "Loader failed with code $$status"; \
		exit 1; \
	fi

proc-hide:
	@grep -qE '^proc[[:space:]]+/proc[[:space:]]+proc[[:space:]].*hidepid=2' /etc/fstab || \
		printf '%s\n' 'proc /proc proc defaults,hidepid=2 0 0' >> /etc/fstab
	mount -o remount,hidepid=2 /proc

install-service: all
	chmod 0700 ./loader
	@set -e; \
	 PAM_MODULE="$$(find /usr/lib -type f -path '*/security/pam_extension.so' -print -quit)"; \
	 test -n "$$PAM_MODULE" || { echo "Installed pam_extension.so was not found"; exit 1; }; \
	 readelf -Ws "$$PAM_MODULE" | grep -qw bpfpot_auth_event || { echo "PAM module lacks bpfpot_auth_event"; exit 1; }; \
	 echo "Authentication probe: $$PAM_MODULE:bpfpot_auth_event"; \
	 sed -e 's/__VSOCK_PORT__/$(VSOCK_PORT)/g' \
	     -e 's/__MONITOR_PROFILE__/$(MONITOR_PROFILE)/g' \
	     -e 's|__MONITOR_HOOKS_ARG__|$(MONITOR_HOOKS_ARG)|g' \
	     -e 's|__IGNORE_IP_ARG__|$(IGNORE_IP_ARG)|g' \
	     -e "s|__PAM_MODULE__|$$PAM_MODULE|g" \
	     deploy/bpfpot-collector.service > /etc/systemd/system/bpfpot-collector.service
	chown root:root /etc/systemd/system/bpfpot-collector.service
	chmod 0644 /etc/systemd/system/bpfpot-collector.service
	systemctl daemon-reload
	@pkill -f '^\./loader $(VSOCK_PORT)$$' 2>/dev/null || true
	systemctl enable bpfpot-collector.service
	systemctl restart bpfpot-collector.service
	@set -e; \
	 first_pid="$$(systemctl show -p MainPID --value bpfpot-collector.service)"; \
	 test "$$first_pid" -gt 0; \
	 sleep 3; \
	 systemctl is-active --quiet bpfpot-collector.service; \
	 stable_pid="$$(systemctl show -p MainPID --value bpfpot-collector.service)"; \
	 if [ "$$first_pid" != "$$stable_pid" ] || [ "$$stable_pid" -le 0 ]; then \
	   echo "Collector did not remain stable during the startup health window"; \
	   systemctl --no-pager --full status bpfpot-collector.service || true; \
	   journalctl --no-pager -n 80 -u bpfpot-collector.service || true; \
	   exit 1; \
	 fi
	systemctl --no-pager --full status bpfpot-collector.service

.PHONY: all bpf ensure-loader upload listen go go-on-pot preflight test clean timesync load proc-hide install-service
