# em-wall — local Makefile (development)
#
# The shipped flow is: build the .app via `make app-bundle`, then end
# users install/uninstall em-wall from inside the app itself (the UI
# escalates via osascript admin prompt). The in-app installer at
# app/internal/installer/ is the only source of truth for what gets
# written where — including the `em-wall` CLI, which ships as another
# embedded resource rather than having its own install path.

GO          ?= go
WAILS       ?= $(HOME)/go/bin/wails
BUILD_DIR   ?= build
DAEMON_BIN  := $(BUILD_DIR)/em-walld
CLI_BIN     := $(BUILD_DIR)/em-wall
APP_DIR     := app
# Vite's output dir, embedded by app/main.go. Gitignored — see test-app.
APP_DIST       := $(APP_DIR)/frontend/dist
APP_DIST_INDEX := $(APP_DIST)/index.html

VERSION     ?= dev
LDFLAGS     := -X github.com/ehsan/em-wall/core/version.Version=$(VERSION)

# Resources embedded into the Wails app binary at build time. Populated
# by the `app-resources` target, read by app/internal/installer/embed.go.
APP_RES_DIR := $(APP_DIR)/internal/installer/resources
APP_RES_BIN     := $(APP_RES_DIR)/em-walld
APP_RES_CLI     := $(APP_RES_DIR)/em-wall
APP_RES_PLIST   := $(APP_RES_DIR)/com.em-wall.daemon.plist
APP_RES_ANCHOR  := $(APP_RES_DIR)/em-wall.pf.anchor

.PHONY: all daemon cli app app-bundle app-resources app-icon test test-core test-app bench lint \
        run-daemon run-daemon-root run-app clean tidy

all: daemon cli app

# daemon is PHONY — `go build` is the only thing that knows whether
# the binary is current relative to its sources, and Go's build cache
# makes a no-op rebuild sub-second. Letting Make decide via mtime on a
# single output file silently misses changes in daemon/*.go, core/**,
# go.mod, etc. and ships a stale daemon to the .app — which is the
# bug that caused "reinstall didn't bring the new methods".
daemon:
	@mkdir -p $(BUILD_DIR)
	$(GO) build -buildvcs=false -ldflags "$(LDFLAGS)" -o $(DAEMON_BIN) ./daemon

# PHONY for the same reason as daemon: only `go build` knows whether
# the binary is current relative to cli/**, core/** and go.mod.
cli:
	@mkdir -p $(BUILD_DIR)
	$(GO) build -buildvcs=false -ldflags "$(LDFLAGS)" -o $(CLI_BIN) ./cli

# `make app` builds the Wails .app *without* the embedded daemon.
# Useful for fast iteration on the UI. The resulting binary will
# refuse to run Install (ErrNotPackaged), so this is dev-only.
app: app-icon
	cd $(APP_DIR) && $(WAILS) build -ldflags "$(LDFLAGS)"

# `make app-bundle` is the primary user-facing build target. It
# refreshes the embedded resources (always rebuilds the daemon from
# source, see above) then runs `wails build` so the resulting .app is
# fully self-contained.
app-bundle: app-resources app-icon
	cd $(APP_DIR) && $(WAILS) build -ldflags "$(LDFLAGS)"

# Stage the daemon binary, plist and pf anchor stub into the embed
# resources dir. Always runs — see the daemon target for why we don't
# trust mtimes here. The cp is cheap; the daemon rebuild is what
# matters.
app-icon:
	@mkdir -p $(APP_DIR)/build
	cp assets/appicon.png $(APP_DIR)/build/appicon.png

app-resources: daemon cli
	@mkdir -p $(APP_RES_DIR)
	cp $(DAEMON_BIN) $(APP_RES_BIN)
	chmod 0755 $(APP_RES_BIN)
	cp $(CLI_BIN) $(APP_RES_CLI)
	chmod 0755 $(APP_RES_CLI)
	cp launchd/com.em-wall.daemon.plist $(APP_RES_PLIST)
	@test -f $(APP_RES_ANCHOR) || printf '# em-wall pf anchor — rewritten at runtime by core/pfctl\n' > $(APP_RES_ANCHOR)

test: test-core test-app
test-core:
	$(GO) test ./core/... ./cli/...

# Hot-path benchmarks (data plane through netstack + splice, rule index).
# No root: the tunnel bench stands a socketpair in for the utun.
bench:
	$(GO) test -run '^$$' -bench . -benchmem ./core/proxytun/ ./core/rules/

# The app module is a separate go.mod, so `go test` from the root never
# reaches it — the installer's generated shell scripts run as root and
# are worth parsing in CI.
test-app: $(APP_DIST_INDEX)
	cd $(APP_DIR) && $(GO) test ./...

# app/main.go does `//go:embed all:frontend/dist`, but that directory is
# gitignored and only ever produced by a real Wails build. On a fresh checkout
# the embed matches nothing and the app package fails to COMPILE — `go test`
# reports "FAIL app [setup failed]" before running anything. It passes on a dev
# machine only because a previous build left a dist behind, so this breaks in CI
# and nowhere else. The root app package has no tests; a stub is all the embed
# needs. `wails build` empties the dir and writes the real bundle, so this
# placeholder never reaches a shipped binary.
$(APP_DIST_INDEX):
	@mkdir -p $(APP_DIST)
	@printf '<!doctype html><title>em-wall</title>\n' > $@

run-daemon: daemon
	@echo "running em-walld with a local DB and socket — no root, no port 53, system DNS untouched"
	mkdir -p tmp
	$(DAEMON_BIN) \
		-db ./tmp/dev.db \
		-socket ./tmp/em-wall.sock \
		-listen 127.0.0.1:5353 \
		-pprof 127.0.0.1:6060 \
		-no-auto-activate

# `run-daemon-root` runs the built daemon under sudo so it can open the
# proxy utun — proxy routing needs root. sudo prompts for your password;
# if you cancel or mistype it, sudo just exits (nothing crashes). Even
# with root, an un-openable utun is non-fatal — the daemon logs
# "proxy routing disabled" and keeps serving.
#
# Uses the DEFAULT socket so `make run-app` pairs with this daemon, but
# keeps -no-auto-activate so it NEVER rewrites system DNS — there's
# nothing to brick if you Ctrl-C it. (Full app-traffic interception
# still needs the DNS hijack; use the installed app for that.)
#
# Don't run this while em-wall is installed: the LaunchDaemon already
# owns /var/run/em-wall.sock and the two would clash.
run-daemon-root: daemon
	@echo "running em-walld as root (sudo will prompt for your password; cancel is safe)…"
	@mkdir -p tmp
	sudo $(DAEMON_BIN) \
		-db ./tmp/dev.db \
		-socket /var/run/em-wall.sock \
		-listen 127.0.0.1:5353 \
		-pprof 127.0.0.1:6060 \
		-no-auto-activate

# `wails dev` builds the app from source. Stage resources first so the
# install panel can be tested end-to-end against the production paths.
run-app: app-resources app-icon
	cd $(APP_DIR) && $(WAILS) dev -ldflags "$(LDFLAGS)"

tidy:
	$(GO) mod tidy
	cd $(APP_DIR) && $(GO) mod tidy

clean:
	rm -rf $(BUILD_DIR) tmp
	rm -f $(APP_RES_BIN) $(APP_RES_CLI) $(APP_RES_PLIST) $(APP_RES_ANCHOR)
	cd $(APP_DIR) && rm -rf frontend/dist build/bin
