208 lines
9.9 KiB
Makefile
208 lines
9.9 KiB
Makefile
# Fenris Makefile
|
|
# Spec: §10.1-10.6
|
|
|
|
SHELL := /bin/bash
|
|
PYTHON := python3
|
|
VENV_DIR := /opt/fenris
|
|
BIN_DIR := /usr/local/bin
|
|
LIBEXEC_DIR := /usr/libexec/fenris
|
|
UNIT_DIR := /etc/systemd/system
|
|
POLKIT_DIR := /usr/share/polkit-1/actions
|
|
CONF_DIR := /etc/fenris
|
|
DATA_DIR := /var/lib/fenris
|
|
|
|
# Placement manifest
|
|
MANIFEST := $(DATA_DIR)/manifest.txt
|
|
|
|
# Legacy history path (IN-4)
|
|
LEGACY_HISTORY := ./data/history.jsonl
|
|
|
|
.PHONY: help install upgrade uninstall purge update-deps test lint check-python check-smartctl import-legacy
|
|
|
|
help:
|
|
@echo "Fenris NVMe endurance monitor"
|
|
@echo ""
|
|
@echo "Targets:"
|
|
@echo " install - Install Fenris (builds wheel, installs to /opt/fenris)"
|
|
@echo " upgrade - Upgrade Fenris (reinstall wheel, sync units)"
|
|
@echo " uninstall - Uninstall Fenris (preserves config and store)"
|
|
@echo " purge - Remove everything including config and store"
|
|
@echo " test - Run tests"
|
|
@echo " lint - Run linter"
|
|
@echo " update-deps - Update dependency pins"
|
|
|
|
# ─── Pre-install gates ──────────────────────────────────────────────────────
|
|
|
|
check-python:
|
|
@echo "=== Verifying Python ≥ 3.9 ==="
|
|
@$(PYTHON) -c "import sys; v=sys.version_info; exit(0 if (v>=(3,9)) else 1)" || { echo "Error: Python 3.9+ required (found $$($(PYTHON) --version 2>&1))"; exit 1; }
|
|
|
|
check-smartctl:
|
|
@echo "=== Verifying smartctl ==="
|
|
@smartctl --version 2>/dev/null | head -1 || { echo "Error: smartctl not found (install smartmontools)"; exit 1; }
|
|
|
|
# ─── Build ──────────────────────────────────────────────────────────────────
|
|
|
|
dist/fenris-*.whl: pyproject.toml src/fenris/*.py
|
|
@mkdir -p dist
|
|
$(PYTHON) -m build --wheel -o dist
|
|
|
|
# ─── Install ────────────────────────────────────────────────────────────────
|
|
|
|
install: check-python check-smartctl dist/fenris-*.whl
|
|
@echo "=== Creating directories ==="
|
|
@sudo mkdir -p $(LIBEXEC_DIR)
|
|
@sudo mkdir -p $(CONF_DIR)
|
|
@sudo mkdir -p $(POLKIT_DIR)
|
|
|
|
@echo "=== Creating data directory (root-written, group-read) ==="
|
|
@sudo groupadd -f fenris
|
|
@sudo install -d -o root -g fenris -m 2750 $(DATA_DIR)
|
|
|
|
@echo "=== Installing venv with pinned dependencies ==="
|
|
@sudo rm -rf $(VENV_DIR)
|
|
@sudo $(PYTHON) -m venv $(VENV_DIR)
|
|
@sudo $(VENV_DIR)/bin/pip install --upgrade pip --quiet
|
|
@sudo $(VENV_DIR)/bin/pip install dist/fenris-*.whl --quiet
|
|
|
|
@echo "=== Installing wrapper ==="
|
|
@sudo install -m 0755 scripts/fenris $(BIN_DIR)/fenris
|
|
|
|
@echo "=== Installing helpers ==="
|
|
@sudo install -m 0755 src/fenris/monitor.py $(LIBEXEC_DIR)/fenris-monitor
|
|
@sudo install -m 0755 src/fenris/collect.py $(LIBEXEC_DIR)/fenris-collect
|
|
|
|
@echo "=== Installing systemd units (dormant — not enabled/started) ==="
|
|
@sudo install -m 0644 units/fenris-collect.timer $(UNIT_DIR)/
|
|
@sudo install -m 0644 units/fenris-collect.service $(UNIT_DIR)/
|
|
@sudo systemctl daemon-reload
|
|
|
|
@echo "=== Installing polkit policy ==="
|
|
@sudo install -m 0644 polkit/com.bongbetic.fenris.monitor.policy $(POLKIT_DIR)/
|
|
|
|
@echo "=== Recording manifest (IN-2, IN-10) ==="
|
|
@echo "# Fenris placement manifest — do not edit" | sudo tee $(MANIFEST) > /dev/null
|
|
@echo "# Generated by: sudo make install" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "# Timestamp: $$(date -u +%%Y-%%m-%%dT%%H:%%M:%%SZ)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(BIN_DIR)/fenris" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(LIBEXEC_DIR)/fenris-monitor" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(LIBEXEC_DIR)/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(UNIT_DIR)/fenris-collect.timer" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(UNIT_DIR)/fenris-collect.service" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(VENV_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(DATA_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(CONF_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(MANIFEST)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
|
|
@echo "=== Install complete ==="
|
|
@echo "Units installed but NOT enabled or started (dormant — IN-3)."
|
|
@echo "To start monitoring: fenris monitor resume"
|
|
|
|
@$(MAKE) --no-print-directory import-legacy
|
|
|
|
# ─── Legacy import (IN-4, ST-7) ────────────────────────────────────────────
|
|
|
|
import-legacy:
|
|
@if [ -f "$(LEGACY_HISTORY)" ]; then \
|
|
echo "=== Detected legacy history: $(LEGACY_HISTORY) ==="; \
|
|
echo "Running idempotent import..."; \
|
|
$(VENV_DIR)/bin/python3 -c "import sys; sys.path.insert(0, 'src'); from fenris.legacy import import_legacy_history; from fenris.store import init_store; from pathlib import Path; conn = init_store(Path('$(DATA_DIR)/observations.db')); r = import_legacy_history(conn, Path('$(LEGACY_HISTORY)')); conn.close(); print(f' Samples imported: {r.get(\"samples_imported\", 0)}'); print(f' Hours imported: {r.get(\"hours_imported\", 0)}'); print(f' Malformed lines: {r.get(\"malformed_lines\", 0)}') if not r.get('skipped') else print(' Skipped: already imported')" || echo " Warning: import failed (non-fatal)"; \
|
|
else \
|
|
echo "=== No legacy history found at $(LEGACY_HISTORY) ==="; \
|
|
fi
|
|
|
|
# ─── Upgrade ────────────────────────────────────────────────────────────────
|
|
|
|
upgrade: dist/fenris-*.whl
|
|
@echo "=== Upgrading Fenris ==="
|
|
@echo "=== Snapshotting database (IN-6) ==="
|
|
@sudo cp $(DATA_DIR)/observations.db $(DATA_DIR)/observations.db.bak 2>/dev/null || true
|
|
|
|
@echo "=== Installing new wheel with pinned dependencies ==="
|
|
@sudo $(VENV_DIR)/bin/pip install dist/fenris-*.whl --quiet
|
|
|
|
@echo "=== Syncing units ==="
|
|
@sudo install -m 0644 units/fenris-collect.timer $(UNIT_DIR)/
|
|
@sudo install -m 0644 units/fenris-collect.service $(UNIT_DIR)/
|
|
@sudo install -m 0644 polkit/com.bongbetic.fenris.monitor.policy $(POLKIT_DIR)/
|
|
@sudo install -m 0755 scripts/fenris $(BIN_DIR)/fenris
|
|
@sudo install -m 0755 src/fenris/monitor.py $(LIBEXEC_DIR)/fenris-monitor
|
|
@sudo install -m 0755 src/fenris/collect.py $(LIBEXEC_DIR)/fenris-collect
|
|
@sudo systemctl daemon-reload
|
|
|
|
@echo "=== Updating manifest ==="
|
|
@echo "# Fenris placement manifest — do not edit" | sudo tee $(MANIFEST) > /dev/null
|
|
@echo "# Generated by: sudo make upgrade" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "# Timestamp: $$(date -u +%%Y-%%m-%%dT%%H:%%M:%%SZ)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(BIN_DIR)/fenris" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(LIBEXEC_DIR)/fenris-monitor" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(LIBEXEC_DIR)/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(UNIT_DIR)/fenris-collect.timer" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(UNIT_DIR)/fenris-collect.service" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(VENV_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(DATA_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(CONF_DIR)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
@echo "$(MANIFEST)" | sudo tee -a $(MANIFEST) > /dev/null
|
|
|
|
@if systemctl is-active --quiet fenris-collect.timer; then \
|
|
echo "=== Restarting timer (active) ==="; \
|
|
sudo systemctl restart fenris-collect.timer; \
|
|
fi
|
|
|
|
@echo "=== Upgrade complete ==="
|
|
|
|
# ─── Uninstall (IN-7) ──────────────────────────────────────────────────────
|
|
|
|
uninstall:
|
|
@echo "=== Uninstalling Fenris ==="
|
|
|
|
@if [ -x $(LIBEXEC_DIR)/fenris-monitor ]; then \
|
|
echo "=== Performing sanctioned disable (§10.4) ==="; \
|
|
sudo $(LIBEXEC_DIR)/fenris-monitor disable --now || true; \
|
|
fi
|
|
|
|
@echo "=== Stopping units ==="
|
|
@sudo systemctl stop fenris-collect.timer 2>/dev/null || true
|
|
@sudo systemctl disable fenris-collect.timer 2>/dev/null || true
|
|
@sudo systemctl daemon-reload
|
|
|
|
@echo "=== Removing installed files (preserving config and store) ==="
|
|
@-rm -f $(BIN_DIR)/fenris
|
|
@-rm -f $(LIBEXEC_DIR)/fenris-monitor
|
|
@-rm -f $(LIBEXEC_DIR)/fenris-collect
|
|
@-rmdir $(LIBEXEC_DIR) 2>/dev/null || true
|
|
@-rm -f $(UNIT_DIR)/fenris-collect.timer
|
|
@-rm -f $(UNIT_DIR)/fenris-collect.service
|
|
@-rm -f $(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy
|
|
@sudo rm -rf $(VENV_DIR)
|
|
@-sudo rm -f $(MANIFEST)
|
|
|
|
@echo "=== Uninstall complete ==="
|
|
@echo "Config preserved at $(CONF_DIR)"
|
|
@echo "Store preserved at $(DATA_DIR)"
|
|
|
|
# ─── Purge ──────────────────────────────────────────────────────────────────
|
|
|
|
purge: uninstall
|
|
@echo "=== Purging Fenris ==="
|
|
@-sudo rm -rf $(CONF_DIR)
|
|
@-sudo rm -rf $(DATA_DIR)
|
|
@echo "=== Purge complete ==="
|
|
|
|
# ─── Test ───────────────────────────────────────────────────────────────────
|
|
|
|
test:
|
|
$(PYTHON) -m pytest tests/ -v
|
|
|
|
# ─── Lint ───────────────────────────────────────────────────────────────────
|
|
|
|
lint:
|
|
$(PYTHON) -m ruff check src/ tests/
|
|
|
|
# ─── Dependencies (IN-8) ───────────────────────────────────────────────────
|
|
|
|
update-deps:
|
|
$(PYTHON) -m pip compile pyproject.toml -o requirements.txt
|