Files
bookhoard/Makefile
T
john-okeefe 604a2458e9 fix(makefile): add non-systemd podman healthcheck workaround
Podman relies on systemd timers to schedule automatic healthchecks. On
non-systemd systems (e.g., Void Linux with runit), healthchecks never
fire, which causes podman-compose to hang forever waiting for
service_healthy conditions that never resolve.

Add two Make macros to handle this transparently:

- compose_up: runs podman compose up -d normally on systemd, but with
  a 15-second timeout on non-systemd to create containers without
  hanging. Supports passing compose flags via $(call compose_up,args).

- ensure_healthy: on non-systemd systems, waits for the database to
  accept connections, manually triggers its healthcheck, starts the app
  container, waits for the app health endpoint, and triggers its
  healthcheck. On systemd systems, the runtime check is skipped entirely
  (zero overhead).

Both macros use a runtime shell check for /run/systemd/system, so the
same Makefile works identically on all systems without parse-time
conditionals.

Applied to all compose-up targets: up, rebuild, rebuild-force,
rebuild-force-db, rebuild-app, rebuild-app-force, test-integration,
and test-env-up.

Refs: https://github.com/containers/podman/pull/27033
2026-05-23 23:48:06 -04:00

207 lines
7.6 KiB
Makefile

.PHONY: help test test-integration test-all rebuild rebuild-force rebuild-app rebuild-app-force rebuild-force-db clean restart up down logs ps test-env-up test-env-down verify-guidelines verify-quick
# Include .env file for environment variables (single source of truth)
# Ignore if .env doesn't exist yet
ifneq (,$(wildcard ./.env))
include .env
export
endif
# On non-systemd systems (e.g., Void Linux with runit), podman healthchecks are
# never triggered automatically because podman relies on systemd timers.
# Additionally, podman-compose up -d blocks forever waiting for service_healthy
# conditions that never resolve. This macro:
# 1. Waits for the DB to accept connections, triggers its healthcheck
# 2. Starts the app container, waits for it, triggers its healthcheck
# On systemd systems, the runtime check skips everything.
# See: https://github.com/containers/podman/pull/27033
define ensure_healthy
@if [ ! -d /run/systemd/system ]; then \
echo "Non-systemd: waiting for database..."; \
i=0; \
until podman exec bookhoard_db pg_isready -U postgres >/dev/null 2>&1; do \
i=$$((i+1)); if [ $$i -gt 30 ]; then echo " Database failed to start"; exit 1; fi; \
sleep 2; \
done; \
podman healthcheck run bookhoard_db >/dev/null 2>&1 || true; \
podman start bookhoard 2>/dev/null || true; \
echo "Non-systemd: waiting for application..."; \
i=0; \
until podman exec bookhoard curl -sf http://localhost:8765/health >/dev/null 2>&1; do \
i=$$((i+1)); if [ $$i -gt 30 ]; then echo " Application failed to start"; exit 1; fi; \
sleep 2; \
done; \
podman healthcheck run bookhoard >/dev/null 2>&1 || true; \
echo " Services healthy"; \
fi
endef
# podman-compose up -d blocks forever on non-systemd waiting for service_healthy.
# This macro runs compose with a timeout on non-systemd to let it create containers
# without hanging. Pass compose flags via the COMPOSE_UP_ARGS variable.
# Usage: $(call compose_up,--build --force-recreate)
# On systemd systems, the timeout is skipped.
define compose_up
@if [ -d /run/systemd/system ]; then \
podman compose up $1 -d; \
else \
timeout 15 podman compose up $1 -d || true; \
fi
endef
# Default target
help:
@echo "Available targets:"
@echo ""
@echo "Development:"
@echo " make up - Start containers (if already built)"
@echo " make rebuild - Rebuild all containers (database preserved)"
@echo " make rebuild-force - Rebuild all containers, no cache (database preserved)"
@echo " make rebuild-app - Rebuild app container only (fast, database preserved)"
@echo " make rebuild-app-force - Rebuild app container only, no cache (database preserved)"
@echo " make rebuild-force-db - Rebuild all containers, no cache (DELETES database)"
@echo " make restart - Restart app container (preserves db)"
@echo " make down - Stop all containers (preserves volumes)"
@echo " make clean - Stop and remove all containers and volumes"
@echo ""
@echo "Status & Logs:"
@echo " make ps - Show container status"
@echo " make logs - Show container logs (follow mode)"
@echo ""
@echo "Testing:"
@echo " make test - Run unit tests (from host, fast)"
@echo " make test-integration - Run integration tests (in containers, matches prod)"
@echo " make test-all - Run all tests (unit + integration)"
@echo " make test-env-up - Start containers with test mode enabled"
@echo " make test-env-down - Stop test environment"
@echo ""
@echo "Verification:"
@echo " make verify-guidelines - Run comprehensive guidelines check"
@echo " make verify-quick - Run quick guidelines check"
# Run unit tests locally (fast, no containers)
test:
go test ./internal/... -v -short
# Run integration tests in containers (matches production environment)
test-integration:
@echo "Building test containers..."
podman compose --profile tests build
@echo "Starting application containers..."
$(call compose_up,db app)
$(ensure_healthy)
@echo "Waiting for services to be healthy..."
@until podman exec bookhoard_db pg_isready -U postgres > /dev/null 2>&1; do \
echo " Database not ready yet..."; \
sleep 2; \
done; \
echo " ✓ Database is ready"
@until podman exec bookhoard curl -sf http://localhost:8765/health > /dev/null 2>&1; do \
echo " Application not ready yet..."; \
sleep 2; \
done; \
echo " ✓ Application is ready"
@echo ""
@echo "Running integration tests in container..."
podman compose --profile tests run --rm tests
@echo ""
@echo "✅ Integration tests completed!"
@echo "📝 Containers are still running. Use 'make logs' to view logs or 'make clean' to stop."
# Run all tests
test-all: test test-integration
# Rebuild app container only (preserve DB, with cache)
rebuild-app:
@echo "Rebuilding app container (database stays running)..."
$(call compose_up,--build --force-recreate app)
$(ensure_healthy)
@echo "✓ App container rebuilt and restarted"
# Rebuild app container only (preserve DB, no cache)
rebuild-app-force:
@echo "Force rebuilding app container (database stays running, no cache)..."
podman compose build --no-cache app
$(call compose_up,--force-recreate app)
$(ensure_healthy)
@echo "✓ App container rebuilt and restarted"
# Rebuild all containers (preserve DB, with cache)
rebuild:
@echo "Rebuilding all containers (database preserved)..."
$(call compose_up,--build --force-recreate)
$(ensure_healthy)
@echo "✓ All containers rebuilt and restarted"
# Rebuild all containers (preserve DB, no cache)
rebuild-force:
@echo "Force rebuilding all containers (database preserved, no cache)..."
podman compose build --no-cache
$(call compose_up,--force-recreate)
$(ensure_healthy)
@echo "✓ All containers rebuilt and restarted"
# Rebuild all containers (remove DB, no cache)
rebuild-force-db:
@echo "Force rebuilding all containers (database will be DELETED, no cache)..."
podman compose down -v
podman compose build --no-cache
$(call compose_up,--force-recreate)
$(ensure_healthy)
@echo "✓ All containers rebuilt and restarted"
# Stop and remove containers
clean:
podman compose down -v
# Quick start (if already built)
up:
$(compose_up)
$(ensure_healthy)
# Stop all containers (alias for clean)
down:
podman compose down
# Restart app container (preserves database)
restart:
@echo "Restarting app container (database stays running)..."
podman compose restart app
@echo "✓ App container restarted"
# Show container status
ps:
podman compose ps
# Show container logs
logs:
podman compose logs -f
# Start containers with test mode enabled for manual testing
test-env-up:
@echo "Starting containers with test mode enabled..."
@if [ -d /run/systemd/system ]; then \
TEST_MODE=true RATE_LIMIT_ENABLED=false REQUESTS_PER_MINUTE=1000 podman compose up --build --force-recreate -d; \
else \
TEST_MODE=true RATE_LIMIT_ENABLED=false REQUESTS_PER_MINUTE=1000 timeout 15 podman compose up --build --force-recreate -d || true; \
fi
$(ensure_healthy)
@echo "Waiting for services to be ready..."
@until podman exec bookhoard_db pg_isready -U postgres > /dev/null 2>&1; do sleep 1; done
@until podman exec bookhoard curl -sf http://localhost:8765/health > /dev/null 2>&1; do sleep 1; done
@echo "✓ Test environment is ready!"
@echo "Application available at http://localhost:8765"
# Stop test environment
test-env-down:
podman compose down -v
# Verify project guidelines compliance
verify-guidelines:
@echo "Running comprehensive project guidelines verification..."
@./scripts/verify-guidelines.sh
verify-quick:
@echo "Running quick project guidelines verification..."
@./scripts/verify-quick.sh