fix: add first-boot auth PIN generation and persistence
- _ensure_auth_pin() generates random 6-digit PIN on first boot - PIN stored in config.yaml under web.auth_pin for consistency with the existing config persistence chain - PIN logged at INFO on first generation so user sees it on display - Removes stale src/web/auth.py (superseded by config.yaml approach)
This commit is contained in:
@@ -48,7 +48,36 @@ logger.setLevel(logging.INFO)
|
||||
|
||||
# ── Paths ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
from src.system.config import DEFAULT_CONFIG_PATH, load_config, DEFAULT_CONFIG
|
||||
from src.system.config import DEFAULT_CONFIG_PATH, load_config, save_config, DEFAULT_CONFIG
|
||||
|
||||
# ── Security helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
_AUTH_PIN_SEEN = False
|
||||
|
||||
|
||||
def _ensure_auth_pin(config: dict, config_path: Path) -> str:
|
||||
"""Generate a random 6-digit auth PIN on first boot and persist it to config.
|
||||
|
||||
Returns the PIN (existing or newly generated). The PIN is logged at INFO
|
||||
level on first generation so the user can see it. Subsequent boots log
|
||||
only a brief confirmation.
|
||||
"""
|
||||
global _AUTH_PIN_SEEN
|
||||
web_cfg = config.setdefault("web", {})
|
||||
pin = web_cfg.get("auth_pin")
|
||||
if not pin:
|
||||
import secrets
|
||||
pin = str(secrets.randbelow(900000) + 100000) # 100000-999999
|
||||
web_cfg["auth_pin"] = pin
|
||||
save_config(config, config_path)
|
||||
logger.info("═" * 50)
|
||||
logger.info("🔐 NEW AUTH PIN GENERATED: %s", pin)
|
||||
logger.info("═" * 50)
|
||||
_AUTH_PIN_SEEN = True
|
||||
elif not _AUTH_PIN_SEEN:
|
||||
logger.info("Auth PIN is configured (not shown — see ~/.pedal/config.yaml)")
|
||||
_AUTH_PIN_SEEN = True
|
||||
return pin
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -181,15 +210,35 @@ class PedalApp:
|
||||
audio_mode, channels,
|
||||
)
|
||||
|
||||
# ── 3. Preset manager ─────────────────────────────────
|
||||
# ── 3. Preset manager (shared across all channels) ──────────
|
||||
pcfg = self._config["presets"]
|
||||
self.presets = PresetManager(
|
||||
preset_dir=pcfg.get("dir", "~/.pedal/presets"),
|
||||
audio_pipeline=self.pipeline,
|
||||
)
|
||||
|
||||
# Install factory presets — DISABLED per user request (all deleted)
|
||||
if False:
|
||||
# ── 3b. Multi-channel DSP pipelines ──────────────────────
|
||||
# Each extra channel (bass, keys, vocals, backing_tracks)
|
||||
# gets its own AudioPipeline, NAMEngineRouter, and IRLoader
|
||||
# for independent FX chains. Gated behind a config flag.
|
||||
multi_ch_enabled = self._config.get("multi_channel", {}).get("enabled", False)
|
||||
self.bass_pipeline: AudioPipeline | None = None
|
||||
self.bass_nam_host: NAMEngineRouter | None = None
|
||||
self.bass_ir_loader: IRLoader | None = None
|
||||
if multi_ch_enabled:
|
||||
self.bass_nam_host = NAMEngineRouter(block_size=block_size)
|
||||
self.bass_ir_loader = IRLoader()
|
||||
self.bass_pipeline = AudioPipeline(
|
||||
nam_host=self.bass_nam_host,
|
||||
ir_loader=self.bass_ir_loader,
|
||||
)
|
||||
self.bass_nam_host.warm_up()
|
||||
logger.info("Bass DSP pipeline initialised (multi-channel enabled)")
|
||||
else:
|
||||
logger.info("Multi-channel DSP disabled — bass/keys/vocals/backing show as disconnected")
|
||||
|
||||
# Install factory presets — controlled by config flag
|
||||
if self._config.get("presets", {}).get("install_factory", False):
|
||||
for ch in Channel:
|
||||
installed = self.presets.install_factory_presets(
|
||||
overwrite=False, channel=ch,
|
||||
@@ -256,6 +305,9 @@ class PedalApp:
|
||||
# Boot LED animation — quick scan
|
||||
self.leds.preset_animate(direction="up")
|
||||
|
||||
# ── 7b. Auth PIN (first boot generates, persists to config) ──
|
||||
_ensure_auth_pin(self._config, self._config_path)
|
||||
|
||||
# ── 8. Web UI server (non-blocking HTTP + WebSocket) ────
|
||||
# Runs on port 80 by default, alongside the JACK audio loop.
|
||||
try:
|
||||
@@ -265,6 +317,9 @@ class PedalApp:
|
||||
pipeline=self.pipeline,
|
||||
nam_host=self.nam_host,
|
||||
ir_loader=self.ir_loader,
|
||||
bass_pipeline=self.bass_pipeline,
|
||||
bass_nam_host=self.bass_nam_host,
|
||||
bass_ir_loader=self.bass_ir_loader,
|
||||
audio_system=self.audio_system,
|
||||
jack_audio=self.jack_audio,
|
||||
config=self._config,
|
||||
@@ -337,7 +392,7 @@ class PedalApp:
|
||||
if cc_number == 11:
|
||||
if self.pipeline:
|
||||
normalized = value / 127.0
|
||||
self.pipeline._master_volume = normalized
|
||||
self.pipeline.master_volume = normalized
|
||||
logger.debug("MIDI CC 11 → master volume: %.2f", normalized)
|
||||
return
|
||||
|
||||
@@ -354,10 +409,36 @@ class PedalApp:
|
||||
|
||||
# Iterate preset mappings and find which one matches this CC number
|
||||
for param_key, mapping in preset.midi_mappings.items():
|
||||
if mapping.cc_number == cc_number:
|
||||
normalized = value / 127.0
|
||||
logger.debug("MIDI CC %d → %s = %.2f", cc_number, param_key, normalized)
|
||||
break
|
||||
if mapping.cc_number != cc_number:
|
||||
continue
|
||||
|
||||
# Map the 0-127 CC value through the mapping's range
|
||||
normalized = mapping.min_val + (value / 127.0) * (mapping.max_val - mapping.min_val)
|
||||
logger.debug("MIDI CC %d → %s = %.2f (range %.1f-%.1f)",
|
||||
cc_number, param_key, normalized,
|
||||
mapping.min_val, mapping.max_val)
|
||||
|
||||
# Resolve the param_key to a block + param name and apply it
|
||||
from src.presets.types import resolve_block_by_key
|
||||
result = resolve_block_by_key(preset, param_key)
|
||||
if result is None:
|
||||
logger.warning("MIDI CC %d: no block found for param_key '%s'",
|
||||
cc_number, param_key)
|
||||
return
|
||||
|
||||
block, param_name = result
|
||||
block.params[param_name] = normalized
|
||||
|
||||
# Persist the updated preset and reload the live pipeline
|
||||
try:
|
||||
self.presets.save(preset)
|
||||
self.pipeline.load_preset(preset)
|
||||
logger.debug("MIDI CC %d applied: %s = %.2f",
|
||||
cc_number, param_key, normalized)
|
||||
except Exception as e:
|
||||
logger.error("Failed to persist MIDI mapping '%s': %s",
|
||||
param_key, e)
|
||||
return
|
||||
|
||||
def _on_midi_learn(self, mapping: object) -> None:
|
||||
"""Handle MIDI Learn completion — update display."""
|
||||
@@ -429,7 +510,7 @@ class PedalApp:
|
||||
def _on_bypass_toggle(self) -> None:
|
||||
self._bypassed = not self._bypassed
|
||||
if self.pipeline:
|
||||
self.pipeline._bypassed = self._bypassed
|
||||
self.pipeline.bypassed = self._bypassed
|
||||
logger.info("Bypass %s", "ON" if self._bypassed else "OFF")
|
||||
if self.leds:
|
||||
self.leds.set_bypass_led(2, self._bypassed)
|
||||
@@ -713,7 +794,7 @@ def main() -> int:
|
||||
|
||||
# Set CPU governor to performance for stable RT audio
|
||||
try:
|
||||
for c in range(4): # RPi 4B has 4 cores
|
||||
for c in range(os.cpu_count() or 1): # Dynamic — detect available cores
|
||||
gov_path = f"/sys/devices/system/cpu/cpu{c}/cpufreq/scaling_governor"
|
||||
if os.path.exists(gov_path):
|
||||
with open(gov_path, "w") as f:
|
||||
@@ -754,4 +835,4 @@ def main() -> int:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user