feat(install): deliver make install (#30)

This commit is contained in:
xavierk
2026-09-02 11:04:16 +05:30
parent bc9b2f8940
commit 1b141206b1
2 changed files with 101 additions and 45 deletions
+90 -45
View File
@@ -12,21 +12,34 @@ CONF_DIR := /etc/fenris
DATA_DIR := /var/lib/fenris DATA_DIR := /var/lib/fenris
# Placement manifest # Placement manifest
MANIFEST := manifest.txt MANIFEST := $(DATA_DIR)/manifest.txt
.PHONY: help install upgrade uninstall purge update-deps test lint # 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: help:
@echo "Fenris NVMe endurance monitor" @echo "Fenris NVMe endurance monitor"
@echo "" @echo ""
@echo "Targets:" @echo "Targets:"
@echo " install - Install Fenris (builds wheel, installs to /opt/fenris)" @echo " install - Install Fenris (builds wheel, installs to /opt/fenris)"
@echo " upgrade - Upgrade Fenris (reinstall wheel, sync units)" @echo " upgrade - Upgrade Fenris (reinstall wheel, sync units)"
@echo " uninstall - Uninstall Fenris (preserves config and store)" @echo " uninstall - Uninstall Fenris (preserves config and store)"
@echo " purge - Remove everything including config and store" @echo " purge - Remove everything including config and store"
@echo " test - Run tests" @echo " test - Run tests"
@echo " lint - Run linter" @echo " lint - Run linter"
@echo " update-deps - Update dependency pins" @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 ────────────────────────────────────────────────────────────────── # ─── Build ──────────────────────────────────────────────────────────────────
@@ -36,25 +49,21 @@ dist/fenris-*.whl: pyproject.toml src/fenris/*.py
# ─── Install ──────────────────────────────────────────────────────────────── # ─── Install ────────────────────────────────────────────────────────────────
install: dist/fenris-*.whl install: check-python check-smartctl dist/fenris-*.whl
@echo "=== Verifying prerequisites ==="
@$(PYTHON) --version 2>/dev/null || (echo "Error: python3 not found"; exit 1)
@smartctl --version 2>/dev/null | head -1 || (echo "Error: smartctl not found"; exit 1)
@echo "=== Creating directories ===" @echo "=== Creating directories ==="
@sudo mkdir -p $(LIBEXEC_DIR) @sudo mkdir -p $(LIBEXEC_DIR)
@sudo mkdir -p $(CONF_DIR) @sudo mkdir -p $(CONF_DIR)
@sudo mkdir -p $(DATA_DIR)
@sudo mkdir -p $(POLKIT_DIR) @sudo mkdir -p $(POLKIT_DIR)
@echo "=== Creating data directory ===" @echo "=== Creating data directory (root-written, group-read) ==="
@sudo install -d -o root -g fenris -m 2750 $(DATA_DIR) 2>/dev/null || sudo groupadd -f fenris && sudo install -d -o root -g fenris -m 2750 $(DATA_DIR) @sudo groupadd -f fenris
@sudo install -d -o root -g fenris -m 2750 $(DATA_DIR)
@echo "=== Installing venv ===" @echo "=== Installing venv with pinned dependencies ==="
@sudo rm -rf $(VENV_DIR) @sudo rm -rf $(VENV_DIR)
@sudo $(PYTHON) -m venv $(VENV_DIR) @sudo $(PYTHON) -m venv $(VENV_DIR)
@sudo $(VENV_DIR)/bin/pip install --upgrade pip @sudo $(VENV_DIR)/bin/pip install --upgrade pip --quiet
@sudo $(VENV_DIR)/bin/pip install dist/fenris-*.whl @sudo $(VENV_DIR)/bin/pip install dist/fenris-*.whl --quiet
@echo "=== Installing wrapper ===" @echo "=== Installing wrapper ==="
@sudo install -m 0755 scripts/fenris $(BIN_DIR)/fenris @sudo install -m 0755 scripts/fenris $(BIN_DIR)/fenris
@@ -63,7 +72,7 @@ install: dist/fenris-*.whl
@sudo install -m 0755 src/fenris/monitor.py $(LIBEXEC_DIR)/fenris-monitor @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 install -m 0755 src/fenris/collect.py $(LIBEXEC_DIR)/fenris-collect
@echo "=== Installing systemd units ===" @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.timer $(UNIT_DIR)/
@sudo install -m 0644 units/fenris-collect.service $(UNIT_DIR)/ @sudo install -m 0644 units/fenris-collect.service $(UNIT_DIR)/
@sudo systemctl daemon-reload @sudo systemctl daemon-reload
@@ -71,28 +80,47 @@ install: dist/fenris-*.whl
@echo "=== Installing polkit policy ===" @echo "=== Installing polkit policy ==="
@sudo install -m 0644 polkit/com.bongbetic.fenris.monitor.policy $(POLKIT_DIR)/ @sudo install -m 0644 polkit/com.bongbetic.fenris.monitor.policy $(POLKIT_DIR)/
@echo "=== Creating manifest ===" @echo "=== Recording manifest (IN-2, IN-10) ==="
@echo "# Fenris placement manifest - do not edit" > $(MANIFEST) @echo "# Fenris placement manifest — do not edit" | sudo tee $(MANIFEST) > /dev/null
@echo "# Generated by install target" >> $(MANIFEST) @echo "# Generated by: sudo make install" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(BIN_DIR)/fenris" >> $(MANIFEST) @echo "# Timestamp: $$(date -u +%%Y-%%m-%%dT%%H:%%M:%%SZ)" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(LIBEXEC_DIR)/fenris-monitor" >> $(MANIFEST) @echo "$(BIN_DIR)/fenris" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(LIBEXEC_DIR)/fenris-collect" >> $(MANIFEST) @echo "$(LIBEXEC_DIR)/fenris-monitor" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(UNIT_DIR)/fenris-collect.timer" >> $(MANIFEST) @echo "$(LIBEXEC_DIR)/fenris-collect" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(UNIT_DIR)/fenris-collect.service" >> $(MANIFEST) @echo "$(UNIT_DIR)/fenris-collect.timer" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy" >> $(MANIFEST) @echo "$(UNIT_DIR)/fenris-collect.service" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(VENV_DIR)" >> $(MANIFEST) @echo "$(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy" | sudo tee -a $(MANIFEST) > /dev/null
@echo "$(DATA_DIR)" >> $(MANIFEST) @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 "=== Install complete ==="
@echo "Units installed but NOT enabled or started." @echo "Units installed but NOT enabled or started (dormant — IN-3)."
@echo "To start monitoring: fenris monitor resume" @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 ────────────────────────────────────────────────────────────────
upgrade: dist/fenris-*.whl upgrade: dist/fenris-*.whl
@echo "=== Upgrading Fenris ===" @echo "=== Upgrading Fenris ==="
@echo "=== Installing new wheel ===" @echo "=== Snapshotting database (IN-6) ==="
@sudo $(VENV_DIR)/bin/pip install dist/fenris-*.whl @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 ===" @echo "=== Syncing units ==="
@sudo install -m 0644 units/fenris-collect.timer $(UNIT_DIR)/ @sudo install -m 0644 units/fenris-collect.timer $(UNIT_DIR)/
@@ -103,27 +131,44 @@ upgrade: dist/fenris-*.whl
@sudo install -m 0755 src/fenris/collect.py $(LIBEXEC_DIR)/fenris-collect @sudo install -m 0755 src/fenris/collect.py $(LIBEXEC_DIR)/fenris-collect
@sudo systemctl daemon-reload @sudo systemctl daemon-reload
# Restart timer only if active and contents changed @echo "=== Updating manifest ==="
@if systemctl is-active --quiet fenris-collect.timer; then echo "=== Restarting timer (active) ==="; sudo systemctl restart fenris-collect.timer; fi @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 ===" @echo "=== Upgrade complete ==="
# ─── Uninstall ────────────────────────────────────────────────────────────── # ─── Uninstall (IN-7) ──────────────────────────────────────────────────────
uninstall: uninstall:
@echo "=== Uninstalling Fenris ===" @echo "=== Uninstalling Fenris ==="
# Sanctioned disable first (§10.4) @if [ -x $(LIBEXEC_DIR)/fenris-monitor ]; then \
@if [ -x $(LIBEXEC_DIR)/fenris-monitor ]; then echo "=== Performing sanctioned disable ==="; sudo $(LIBEXEC_DIR)/fenris-monitor disable --now || true; fi echo "=== Performing sanctioned disable (§10.4) ==="; \
sudo $(LIBEXEC_DIR)/fenris-monitor disable --now || true; \
fi
# Stop and disable units
@echo "=== Stopping units ===" @echo "=== Stopping units ==="
@sudo systemctl stop fenris-collect.timer 2>/dev/null || true @sudo systemctl stop fenris-collect.timer 2>/dev/null || true
@sudo systemctl disable fenris-collect.timer 2>/dev/null || true @sudo systemctl disable fenris-collect.timer 2>/dev/null || true
@sudo systemctl daemon-reload @sudo systemctl daemon-reload
# Remove installed files (preserving /etc/fenris and /var/lib/fenris) @echo "=== Removing installed files (preserving config and store) ==="
@echo "=== Removing files ==="
@-rm -f $(BIN_DIR)/fenris @-rm -f $(BIN_DIR)/fenris
@-rm -f $(LIBEXEC_DIR)/fenris-monitor @-rm -f $(LIBEXEC_DIR)/fenris-monitor
@-rm -f $(LIBEXEC_DIR)/fenris-collect @-rm -f $(LIBEXEC_DIR)/fenris-collect
@@ -132,7 +177,7 @@ uninstall:
@-rm -f $(UNIT_DIR)/fenris-collect.service @-rm -f $(UNIT_DIR)/fenris-collect.service
@-rm -f $(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy @-rm -f $(POLKIT_DIR)/com.bongbetic.fenris.monitor.policy
@sudo rm -rf $(VENV_DIR) @sudo rm -rf $(VENV_DIR)
@-rm -f $(MANIFEST) @-sudo rm -f $(MANIFEST)
@echo "=== Uninstall complete ===" @echo "=== Uninstall complete ==="
@echo "Config preserved at $(CONF_DIR)" @echo "Config preserved at $(CONF_DIR)"
@@ -156,7 +201,7 @@ test:
lint: lint:
$(PYTHON) -m ruff check src/ tests/ $(PYTHON) -m ruff check src/ tests/
# ─── Dependencies ─────────────────────────────────────────────────────────── # ─── Dependencies (IN-8) ───────────────────────────────────────────────────
update-deps: update-deps:
$(PYTHON) -m pip compile pyproject.toml -o requirements.txt $(PYTHON) -m pip compile pyproject.toml -o requirements.txt
+11
View File
@@ -0,0 +1,11 @@
# Fenris dependency lockfile
# Exact pins for reproducible installs (IN-8)
# Refresh with: make update-deps
textual==8.2.8
rich==15.0.0
markdown-it-py==4.2.0
mdit-py-plugins==0.6.1
mdurl==0.1.2
platformdirs==4.11.7
Pygments==2.21.0
linkify-it-py==2.2.0