feat: ship Rust login manager and GitLab release pipeline
Replace the legacy Go implementation as the production path, quarantine the reference tree, add deterministic release packaging and version metadata, and run verification through GitLab CI.
This commit is contained in:
272
Makefile
272
Makefile
@@ -1,146 +1,168 @@
|
||||
BINARY := latchd
|
||||
SRC_DIR := src
|
||||
CMD_DIR := $(SRC_DIR)
|
||||
BUILD_DIR := build
|
||||
SHELL := /usr/bin/env bash
|
||||
.SHELLFLAGS := -eu -o pipefail -c
|
||||
.DEFAULT_GOAL := help
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
PREFIX ?= /usr
|
||||
BINDIR := $(DESTDIR)$(PREFIX)/bin
|
||||
CONFDIR := $(DESTDIR)/etc/$(BINARY)
|
||||
SYSTEMD := $(DESTDIR)/usr/lib/systemd/system
|
||||
PROJECT := latchd
|
||||
BINARY := latchd
|
||||
PREFIX ?= /usr
|
||||
DESTDIR ?=
|
||||
BINDIR := $(DESTDIR)$(PREFIX)/bin
|
||||
CONFDIR := $(DESTDIR)/etc/$(PROJECT)
|
||||
SYSTEMD_DIR := $(DESTDIR)/usr/lib/systemd/system
|
||||
BUILD_DIR ?= build
|
||||
DIST_DIR ?= dist
|
||||
CARGO_TARGET_DIR ?= target
|
||||
CARGO ?= cargo
|
||||
GO ?= go
|
||||
TARGET ?=
|
||||
TAG ?=
|
||||
|
||||
VERSION ?= 0.1.0
|
||||
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
BUILDDATE := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
VERSION ?= $(shell sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
GIT_COMMIT ?= $(shell git rev-parse --short=12 HEAD 2>/dev/null || printf unknown)
|
||||
SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct 2>/dev/null || date +%s)
|
||||
TARGET_TRIPLE ?= $(shell rustc -vV 2>/dev/null | sed -n 's/^host: //p' || printf unknown)
|
||||
|
||||
LDFLAGS := -s -w \
|
||||
-X main.Version=$(VERSION) \
|
||||
-X main.Commit=$(COMMIT) \
|
||||
-X main.BuildTime=$(BUILDDATE)
|
||||
TARGET_ARGS := $(if $(TARGET),--target $(TARGET),)
|
||||
RELEASE_DIR := $(CARGO_TARGET_DIR)/$(if $(TARGET),$(TARGET)/)release
|
||||
PACKAGE_NAME := $(PROJECT)-$(VERSION)-$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))
|
||||
PACKAGE_FILE := $(DIST_DIR)/$(PACKAGE_NAME).tar.gz
|
||||
PACKAGE_STAGE := $(DIST_DIR)/stage/$(PACKAGE_NAME)
|
||||
CARGO_ENV := LATCHD_VERSION=$(VERSION) LATCHD_COMMIT=$(GIT_COMMIT) LATCHD_BUILD_TIME=$(SOURCE_DATE_EPOCH) SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)
|
||||
|
||||
GO := go
|
||||
GOFLAGS := -trimpath -ldflags="$(LDFLAGS)"
|
||||
COLOR ?= 1
|
||||
ifeq ($(COLOR),1)
|
||||
RESET := \033[0m
|
||||
DIM := \033[2m
|
||||
CYAN := \033[36m
|
||||
GREEN := \033[32m
|
||||
YELLOW := \033[33m
|
||||
RED := \033[31m
|
||||
else
|
||||
RESET :=
|
||||
DIM :=
|
||||
CYAN :=
|
||||
GREEN :=
|
||||
YELLOW :=
|
||||
RED :=
|
||||
endif
|
||||
|
||||
## ansi helpers
|
||||
BOLD := \033[1m
|
||||
DIM := \033[2m
|
||||
RED := \033[31m
|
||||
GREEN := \033[32m
|
||||
YELLOW := \033[33m
|
||||
BLUE := \033[34m
|
||||
CYAN := \033[36m
|
||||
WHITE := \033[37m
|
||||
RST := \033[0m
|
||||
.PHONY: all deps build release-build test check fmt lint doc audit docker-test \
|
||||
package release release-check version install uninstall clean run-preview \
|
||||
legacy-build legacy-check enable update help
|
||||
|
||||
## ─── targets ────────────────────────────────────────────────
|
||||
all: release-build ## Build the optimized release binary
|
||||
|
||||
.PHONY: all deps build install uninstall clean test lint fmt vet \
|
||||
run-preview release enable update help
|
||||
version: ## Print the package, commit, and reproducibility metadata
|
||||
@printf 'version=%s\ncommit=%s\nepoch=%s\ntarget=%s\n' \
|
||||
'$(VERSION)' '$(GIT_COMMIT)' '$(SOURCE_DATE_EPOCH)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))'
|
||||
|
||||
deps: ## install system dependencies
|
||||
@printf " $(BLUE)%-10s$(RST) checking dependencies...\n" "DEPS"
|
||||
@if ! dpkg -s libpam0g-dev >/dev/null 2>&1; then \
|
||||
printf " $(YELLOW)%-10s$(RST) sudo apt install -y libpam0g-dev\n" ""; \
|
||||
sudo apt install -y libpam0g-dev; \
|
||||
deps: ## Check native dependencies needed for builds and tests
|
||||
@command -v $(CARGO) >/dev/null || { echo 'cargo (Rust 1.88+) is required'; exit 1; }
|
||||
@command -v rustfmt >/dev/null || { echo 'rustfmt is required'; exit 1; }
|
||||
@command -v pkg-config >/dev/null || { echo 'pkg-config is required'; exit 1; }
|
||||
@pkg-config --exists pam || { echo 'libpam development files are required'; exit 1; }
|
||||
@printf '$(GREEN)dependencies ok$(RESET)\n'
|
||||
|
||||
build: release-check ## Build and copy the optimized binary to build/
|
||||
@mkdir -p '$(BUILD_DIR)'
|
||||
@printf '$(CYAN)building$(RESET) %s %s\n' '$(VERSION)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))'
|
||||
@env $(CARGO_ENV) $(CARGO) build --release --locked $(TARGET_ARGS)
|
||||
@install -m755 '$(RELEASE_DIR)/$(BINARY)' '$(BUILD_DIR)/$(BINARY)'
|
||||
@'$(BUILD_DIR)/$(BINARY)' --version
|
||||
|
||||
release-build: build ## Alias for the optimized release build
|
||||
|
||||
fmt: ## Check Rust formatting
|
||||
@$(CARGO) fmt --all -- --check
|
||||
|
||||
test: ## Run all Rust tests
|
||||
@$(CARGO) test --locked --workspace --all-targets
|
||||
|
||||
lint: ## Run Clippy with warnings denied
|
||||
@$(CARGO) clippy --locked --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
doc: ## Build rustdoc with warnings denied
|
||||
@RUSTDOCFLAGS='-D warnings' $(CARGO) doc --locked --workspace --no-deps
|
||||
|
||||
audit: ## Run cargo-audit when installed
|
||||
@command -v cargo-audit >/dev/null || { echo 'cargo-audit is required; install with cargo install cargo-audit --locked'; exit 1; }
|
||||
@cargo audit
|
||||
|
||||
check: fmt test lint doc ## Run the complete local verification gate
|
||||
|
||||
docker-test: ## Run the pinned Docker verification suite
|
||||
@./scripts/test-docker.sh
|
||||
|
||||
release-check: ## Validate a release version and optional tag
|
||||
@[[ '$(VERSION)' =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$$ ]] || { echo 'invalid VERSION: $(VERSION)'; exit 1; }
|
||||
@cargo_version="$$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)"; \
|
||||
[[ "$$cargo_version" == '$(VERSION)' ]] || { echo "Cargo version $$cargo_version does not match $(VERSION)"; exit 1; }
|
||||
@if [[ -n '$(TAG)' ]]; then \
|
||||
tag_version="$$(printf %s '$(TAG)' | sed 's/^v//')"; \
|
||||
[[ "$$tag_version" == '$(VERSION)' ]] || { echo "tag $(TAG) does not match $(VERSION)"; exit 1; }; \
|
||||
fi
|
||||
@if ! dpkg -s build-essential >/dev/null 2>&1; then \
|
||||
printf " $(YELLOW)%-10s$(RST) sudo apt install -y build-essential\n" ""; \
|
||||
sudo apt install -y build-essential; \
|
||||
fi
|
||||
@if ! command -v go >/dev/null 2>&1; then \
|
||||
printf " $(RED)%-10s$(RST) go not found — install go 1.22+\n" ""; \
|
||||
exit 1; \
|
||||
fi
|
||||
@printf " $(GREEN)%-10s$(RST) dependencies ok\n" "DEPS"
|
||||
|
||||
all: build ## build the release binary
|
||||
package: release-check build ## Create a deterministic install archive and checksum
|
||||
@rm -rf '$(PACKAGE_STAGE)'
|
||||
@mkdir -p '$(PACKAGE_STAGE)/usr/bin' '$(PACKAGE_STAGE)/usr/lib/systemd/system' \
|
||||
'$(PACKAGE_STAGE)/etc/pam.d' '$(PACKAGE_STAGE)/etc/$(PROJECT)/wms' '$(PACKAGE_STAGE)/etc/$(PROJECT)/wayland'
|
||||
@install -m755 '$(BUILD_DIR)/$(BINARY)' '$(PACKAGE_STAGE)/usr/bin/$(BINARY)'
|
||||
@install -m644 extra/config.toml '$(PACKAGE_STAGE)/etc/$(PROJECT)/config.toml'
|
||||
@install -m644 extra/latchd.pam '$(PACKAGE_STAGE)/etc/pam.d/$(PROJECT)'
|
||||
@install -m644 extra/latchd.service '$(PACKAGE_STAGE)/usr/lib/systemd/system/$(PROJECT).service'
|
||||
@install -m644 README.md LICENSE CHANGELOG.md '$(PACKAGE_STAGE)/'
|
||||
@printf 'version=%s\ncommit=%s\nepoch=%s\ntarget=%s\n' \
|
||||
'$(VERSION)' '$(GIT_COMMIT)' '$(SOURCE_DATE_EPOCH)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))' \
|
||||
> '$(PACKAGE_STAGE)/BUILD-METADATA'
|
||||
@mkdir -p '$(DIST_DIR)'
|
||||
@tar --sort=name --numeric-owner --owner=0 --group=0 --mtime='@$(SOURCE_DATE_EPOCH)' \
|
||||
-C '$(DIST_DIR)/stage' -czf '$(PACKAGE_FILE)' '$(PACKAGE_NAME)'
|
||||
@(cd '$(DIST_DIR)' && sha256sum '$(notdir $(PACKAGE_FILE))' > '$(notdir $(PACKAGE_FILE)).sha256')
|
||||
@(cd '$(DIST_DIR)' && sha256sum --check '$(notdir $(PACKAGE_FILE)).sha256')
|
||||
@checksum="$$(sha256sum '$(PACKAGE_FILE)' | cut -d' ' -f1)"; \
|
||||
printf 'package=%s\nsha256=%s\nversion=%s\n' \
|
||||
'$(PACKAGE_FILE)' "$$checksum" '$(VERSION)' > '$(DIST_DIR)/release.env'
|
||||
@printf '$(GREEN)created$(RESET) %s\n' '$(PACKAGE_FILE)'
|
||||
|
||||
build: $(BUILD_DIR)/$(BINARY) ## compile latchd
|
||||
release: check audit docker-test package ## Run all gates and produce release artifacts
|
||||
|
||||
$(BUILD_DIR)/$(BINARY): $(shell find $(SRC_DIR) -name '*.go')
|
||||
@printf " $(CYAN)%-10s$(RST) %s\n" "GO" "$@"
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
@cd $(SRC_DIR) && $(GO) build $(GOFLAGS) -o ../$@ .
|
||||
install: build ## Install the binary, config, PAM policy, and systemd unit
|
||||
@install -Dm755 '$(BUILD_DIR)/$(BINARY)' '$(BINDIR)/$(BINARY)'
|
||||
@install -Dm644 extra/config.toml '$(CONFDIR)/config.toml'
|
||||
@install -Dm644 extra/latchd.pam '$(DESTDIR)/etc/pam.d/$(PROJECT)'
|
||||
@install -Dm644 extra/latchd.service '$(SYSTEMD_DIR)/$(PROJECT).service'
|
||||
@install -d '$(CONFDIR)/wms' '$(CONFDIR)/wayland'
|
||||
|
||||
install: build ## install to $(PREFIX)
|
||||
@printf " $(GREEN)%-10s$(RST) %s\n" "INSTALL" "$(BINDIR)/$(BINARY)"
|
||||
@install -Dm755 $(BUILD_DIR)/$(BINARY) $(BINDIR)/$(BINARY)
|
||||
@install -Dm644 extra/config.toml $(CONFDIR)/config.toml
|
||||
@install -Dm755 extra/xsetup.sh $(CONFDIR)/xsetup.sh
|
||||
@install -Dm644 extra/latchd.pam $(DESTDIR)/etc/pam.d/$(BINARY)
|
||||
@install -Dm644 extra/latchd.service $(SYSTEMD)/$(BINARY).service
|
||||
@mkdir -p $(CONFDIR)/wms $(CONFDIR)/wayland
|
||||
@printf " $(GREEN)%-10s$(RST) done — run 'systemctl enable $(BINARY)' to activate\n" "INSTALL"
|
||||
uninstall: ## Remove files installed by make install
|
||||
@rm -f '$(BINDIR)/$(BINARY)' '$(DESTDIR)/etc/pam.d/$(PROJECT)' '$(SYSTEMD_DIR)/$(PROJECT).service'
|
||||
@printf '$(YELLOW)configuration at %s was preserved; remove it manually if no longer needed$(RESET)\n' '$(CONFDIR)'
|
||||
|
||||
uninstall: ## remove installed files
|
||||
@printf " $(RED)%-10s$(RST) %s\n" "RM" "$(BINDIR)/$(BINARY)"
|
||||
@rm -f $(BINDIR)/$(BINARY)
|
||||
@rm -rf $(CONFDIR)
|
||||
@rm -f $(DESTDIR)/etc/pam.d/$(BINARY)
|
||||
@rm -f $(SYSTEMD)/$(BINARY).service
|
||||
clean: ## Remove generated build, release, and Cargo artifacts
|
||||
@rm -rf '$(BUILD_DIR)' '$(DIST_DIR)'
|
||||
@$(CARGO) clean
|
||||
|
||||
clean: ## remove build artifacts
|
||||
@printf " $(RED)%-10s$(RST) %s\n" "RM" "$(BUILD_DIR)"
|
||||
@rm -rf $(BUILD_DIR)
|
||||
run-preview: build ## Run the safe non-authenticating preview UI
|
||||
@'$(BUILD_DIR)/$(BINARY)' --preview --no-log
|
||||
|
||||
test: ## run tests
|
||||
@printf " $(YELLOW)%-10s$(RST) running tests...\n" "TEST"
|
||||
@cd $(SRC_DIR) && $(GO) test ./...
|
||||
legacy-build: ## Build the quarantined Go reference only when explicitly requested
|
||||
@printf '$(YELLOW)legacy Go is reference-only and excluded from release builds$(RESET)\n'
|
||||
@command -v $(GO) >/dev/null || { echo 'go is required for legacy-build'; exit 1; }
|
||||
@mkdir -p '$(BUILD_DIR)'
|
||||
@cd legacy/go && $(GO) build -trimpath -o '../../$(BUILD_DIR)/$(BINARY)-go' .
|
||||
|
||||
lint: ## run linter
|
||||
@printf " $(YELLOW)%-10s$(RST) linting...\n" "LINT"
|
||||
@golangci-lint run $(SRC_DIR)/... 2>/dev/null || printf " $(DIM)%-10s$(RST) golangci-lint not found; skipping\n" ""
|
||||
legacy-check: ## Format-check and test the quarantined Go reference
|
||||
@command -v $(GO) >/dev/null || { echo 'go is required for legacy-check'; exit 1; }
|
||||
@command -v gofmt >/dev/null || { echo 'gofmt is required for legacy-check'; exit 1; }
|
||||
@test -z "$$(find legacy/go -type f -name '*.go' -print0 | xargs -0 gofmt -l)" || { echo 'legacy Go files need gofmt'; exit 1; }
|
||||
@cd legacy/go && $(GO) test ./...
|
||||
|
||||
fmt: ## format source
|
||||
@printf " $(BLUE)%-10s$(RST) formatting...\n" "FMT"
|
||||
@cd $(SRC_DIR) && $(GO) fmt ./...
|
||||
enable: ## Enable the systemd service after host validation
|
||||
@sudo systemctl enable $(PROJECT).service
|
||||
|
||||
vet: ## run go vet
|
||||
@printf " $(YELLOW)%-10s$(RST) vetting...\n" "VET"
|
||||
@cd $(SRC_DIR) && $(GO) vet ./...
|
||||
|
||||
run-preview: build ## launch preview mode
|
||||
@printf " $(GREEN)%-10s$(RST) starting preview...\n" "RUN"
|
||||
@./$(BUILD_DIR)/$(BINARY) --preview --no-log
|
||||
|
||||
release: clean ## build with version info
|
||||
@printf " $(BOLD)$(CYAN)╔══════════════════════════════════════╗$(RST)\n"
|
||||
@printf " $(BOLD)$(CYAN)║ latchd $(VERSION) — release build ║$(RST)\n"
|
||||
@printf " $(BOLD)$(CYAN)╚══════════════════════════════════════╝$(RST)\n"
|
||||
@printf "\n"
|
||||
@printf " $(DIM)commit$(RST) $(COMMIT)\n"
|
||||
@printf " $(DIM)built$(RST) $(BUILDDATE)\n"
|
||||
@printf "\n"
|
||||
@$(MAKE) build
|
||||
|
||||
enable: ## disable other DM + enable latchd
|
||||
@printf " $(YELLOW)%-10s$(RST) switching to latchd...\n" "ENABLE"
|
||||
@if systemctl is-active display-manager.service >/dev/null 2>&1; then \
|
||||
printf " $(DIM)%-10s$(RST) stopping current display manager\n" ""; \
|
||||
sudo systemctl stop display-manager.service; \
|
||||
fi
|
||||
@if [ -L /etc/systemd/system/display-manager.service ] \
|
||||
&& ! readlink /etc/systemd/system/display-manager.service | grep -q $(BINARY); then \
|
||||
printf " $(DIM)%-10s$(RST) disabling aliased display manager\n" ""; \
|
||||
sudo systemctl disable display-manager.service; \
|
||||
fi
|
||||
@sudo systemctl enable $(BINARY).service
|
||||
@printf " $(GREEN)%-10s$(RST) latchd enabled — reboot or run 'sudo systemctl start latchd'\n" "ENABLE"
|
||||
|
||||
update: ## uninstall → git pull → rebuild → reinstall
|
||||
@printf " $(YELLOW)%-10s$(RST) removing previous install...\n" "UPDATE"
|
||||
@sudo $(MAKE) uninstall
|
||||
@printf " $(CYAN)%-10s$(RST) pulling latest...\n" "UPDATE"
|
||||
@git pull
|
||||
@printf " $(BLUE)%-10s$(RST) rebuilding...\n" "UPDATE"
|
||||
update: ## Rebuild and reinstall the current checkout without changing git state
|
||||
@sudo $(MAKE) install
|
||||
@printf " $(GREEN)%-10s$(RST) updated — run 'sudo make enable' to switch DMs\n" "UPDATE"
|
||||
|
||||
help: ## print this help
|
||||
@printf " $(BOLD)latchd$(RST) — tui display manager\n"
|
||||
@printf " $(DIM)version $(VERSION) commit $(COMMIT)$(RST)\n"
|
||||
@printf "\n"
|
||||
@printf " $(WHITE)targets:$(RST)\n"
|
||||
@awk -F ':|##' '/^[a-zA-Z].*:.*##/ { \
|
||||
printf " $(CYAN)%-16s$(RST) %s\n", $$1, $$NF }' $(MAKEFILE_LIST) \
|
||||
| sort
|
||||
@printf "\n"
|
||||
help: ## Show available targets
|
||||
@printf '$(CYAN)$(PROJECT)$(RESET) $(VERSION) — production build targets\n\n'
|
||||
@awk -F ':|##' '/^[a-zA-Z0-9_-]+:.*##/ { printf " %-18s %s\n", $$1, $$NF }' '$(MAKEFILE_LIST)' | sort
|
||||
|
||||
Reference in New Issue
Block a user