forked from Rockachopa/Timmy-time-dashboard
feat: centralize L402 config, automate Metal install, fix watchdog cleanup
- config.py: add L402_HMAC_SECRET, L402_MACAROON_SECRET, LIGHTNING_BACKEND to pydantic-settings with startup warnings for default secrets - l402_proxy.py, mock_backend.py, factory.py: migrate from os.environ.get() to `from config import settings` per project convention - Makefile: `make install-creative` now auto-installs PyTorch nightly with Metal (MPS) support on Apple Silicon instead of just printing a note - activate_self_tdd.sh: add PID file (.watchdog.pid) and EXIT trap so Ctrl-C cleanly stops both the dashboard and the watchdog process - .gitignore: add .watchdog.pid https://claude.ai/code/session_01A81E5HMxZEPxzv2acNo35u
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,6 +21,9 @@ env/
|
|||||||
# SQLite memory — never commit agent memory
|
# SQLite memory — never commit agent memory
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
# Runtime PID files
|
||||||
|
.watchdog.pid
|
||||||
|
|
||||||
# Chat platform state files (contain bot tokens)
|
# Chat platform state files (contain bot tokens)
|
||||||
telegram_state.json
|
telegram_state.json
|
||||||
discord_state.json
|
discord_state.json
|
||||||
|
|||||||
14
Makefile
14
Makefile
@@ -27,14 +27,14 @@ install-bigbrain: $(VENV)/bin/activate
|
|||||||
|
|
||||||
install-creative: $(VENV)/bin/activate
|
install-creative: $(VENV)/bin/activate
|
||||||
$(PIP) install --quiet -e ".[dev,creative]"
|
$(PIP) install --quiet -e ".[dev,creative]"
|
||||||
@if [ "$$(uname -s)" = "Darwin" ]; then \
|
@if [ "$$(uname -m)" = "arm64" ] && [ "$$(uname -s)" = "Darwin" ]; then \
|
||||||
echo ""; \
|
echo " Apple Silicon detected — installing PyTorch with Metal (MPS) support..."; \
|
||||||
echo " Note: PyTorch on macOS uses CPU by default."; \
|
$(PIP) install --quiet --pre torch torchvision torchaudio \
|
||||||
echo " For Metal (GPU) acceleration, install the nightly build:"; \
|
--index-url https://download.pytorch.org/whl/nightly/cpu; \
|
||||||
echo " pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu"; \
|
echo "✓ Creative extras installed with Metal GPU acceleration"; \
|
||||||
echo ""; \
|
else \
|
||||||
|
echo "✓ Creative extras installed (diffusers, torch, ace-step)"; \
|
||||||
fi
|
fi
|
||||||
@echo "✓ Creative extras installed (diffusers, torch, ace-step)"
|
|
||||||
|
|
||||||
$(VENV)/bin/activate:
|
$(VENV)/bin/activate:
|
||||||
python3 -m venv $(VENV)
|
python3 -m venv $(VENV)
|
||||||
|
|||||||
@@ -60,15 +60,37 @@ python -m pytest "$REPO_DIR/tests/" -q --tb=short
|
|||||||
echo "==> All tests passed."
|
echo "==> All tests passed."
|
||||||
|
|
||||||
# ── 4. Self-TDD watchdog (background) ────────────────────────────────────────
|
# ── 4. Self-TDD watchdog (background) ────────────────────────────────────────
|
||||||
|
WATCHDOG_PID_FILE="$REPO_DIR/.watchdog.pid"
|
||||||
|
|
||||||
|
# Kill any previously orphaned watchdog
|
||||||
|
if [[ -f "$WATCHDOG_PID_FILE" ]]; then
|
||||||
|
OLD_PID=$(cat "$WATCHDOG_PID_FILE")
|
||||||
|
if kill -0 "$OLD_PID" 2>/dev/null; then
|
||||||
|
echo "==> Stopping previous watchdog (PID $OLD_PID)..."
|
||||||
|
kill "$OLD_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -f "$WATCHDOG_PID_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Starting self-TDD watchdog (60s interval) in background..."
|
echo "==> Starting self-TDD watchdog (60s interval) in background..."
|
||||||
self-tdd watch --interval 60 &
|
self-tdd watch --interval 60 &
|
||||||
WATCHDOG_PID=$!
|
WATCHDOG_PID=$!
|
||||||
echo " Watchdog PID: $WATCHDOG_PID"
|
echo "$WATCHDOG_PID" > "$WATCHDOG_PID_FILE"
|
||||||
|
echo " Watchdog PID: $WATCHDOG_PID (saved to .watchdog.pid)"
|
||||||
echo " Kill with: kill $WATCHDOG_PID"
|
echo " Kill with: kill $WATCHDOG_PID"
|
||||||
|
|
||||||
|
# Clean up watchdog when the script exits (Ctrl-C, etc.)
|
||||||
|
cleanup() {
|
||||||
|
echo ""
|
||||||
|
echo "==> Stopping watchdog (PID $WATCHDOG_PID)..."
|
||||||
|
kill "$WATCHDOG_PID" 2>/dev/null || true
|
||||||
|
rm -f "$WATCHDOG_PID_FILE"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
# ── 5. Dashboard ─────────────────────────────────────────────────────────────
|
# ── 5. Dashboard ─────────────────────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Starting Timmy Time dashboard at http://localhost:8000"
|
echo "==> Starting Timmy Time dashboard at http://localhost:8000"
|
||||||
echo " Ctrl-C stops the dashboard (watchdog continues until you kill it)"
|
echo " Ctrl-C stops both the dashboard and the watchdog"
|
||||||
echo ""
|
echo ""
|
||||||
uvicorn dashboard.app:app --reload --host 0.0.0.0 --port 8000
|
uvicorn dashboard.app:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ class Settings(BaseSettings):
|
|||||||
video_transition_duration: float = 1.0
|
video_transition_duration: float = 1.0
|
||||||
default_video_codec: str = "libx264"
|
default_video_codec: str = "libx264"
|
||||||
|
|
||||||
|
# ── L402 Lightning ───────────────────────────────────────────────────
|
||||||
|
# HMAC secrets for macaroon signing and invoice verification.
|
||||||
|
# MUST be changed from defaults before deploying to production.
|
||||||
|
# Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
l402_hmac_secret: str = "timmy-hmac-secret"
|
||||||
|
l402_macaroon_secret: str = "timmy-macaroon-secret"
|
||||||
|
lightning_backend: Literal["mock", "lnd"] = "mock"
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_file=".env",
|
env_file=".env",
|
||||||
env_file_encoding="utf-8",
|
env_file_encoding="utf-8",
|
||||||
@@ -67,3 +75,20 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
||||||
|
# ── Startup validation ───────────────────────────────────────────────────────
|
||||||
|
# Warn when security-sensitive settings are using defaults.
|
||||||
|
import logging as _logging
|
||||||
|
|
||||||
|
_startup_logger = _logging.getLogger("config")
|
||||||
|
|
||||||
|
if settings.l402_hmac_secret == "timmy-hmac-secret":
|
||||||
|
_startup_logger.warning(
|
||||||
|
"SEC: L402_HMAC_SECRET is using the default value — "
|
||||||
|
"set a unique secret in .env before deploying to production."
|
||||||
|
)
|
||||||
|
if settings.l402_macaroon_secret == "timmy-macaroon-secret":
|
||||||
|
_startup_logger.warning(
|
||||||
|
"SEC: L402_MACAROON_SECRET is using the default value — "
|
||||||
|
"set a unique secret in .env before deploying to production."
|
||||||
|
)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from config import settings
|
||||||
from lightning.base import LightningBackend
|
from lightning.base import LightningBackend
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -68,7 +69,7 @@ def get_backend(name: Optional[str] = None) -> LightningBackend:
|
|||||||
"""
|
"""
|
||||||
_register_backends()
|
_register_backends()
|
||||||
|
|
||||||
backend_name = (name or os.environ.get("LIGHTNING_BACKEND", "mock")).lower()
|
backend_name = (name or settings.lightning_backend).lower()
|
||||||
|
|
||||||
if backend_name not in _BACKENDS:
|
if backend_name not in _BACKENDS:
|
||||||
available = ", ".join(_BACKENDS.keys())
|
available = ", ".join(_BACKENDS.keys())
|
||||||
@@ -100,8 +101,8 @@ def get_backend_info() -> dict:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with backend info for health/status endpoints
|
Dict with backend info for health/status endpoints
|
||||||
"""
|
"""
|
||||||
backend_name = os.environ.get("LIGHTNING_BACKEND", "mock")
|
backend_name = settings.lightning_backend
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"configured_backend": backend_name,
|
"configured_backend": backend_name,
|
||||||
"available_backends": list_backends(),
|
"available_backends": list_backends(),
|
||||||
|
|||||||
@@ -12,20 +12,13 @@ import secrets
|
|||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from config import settings
|
||||||
from lightning.base import Invoice, LightningBackend, LightningError
|
from lightning.base import Invoice, LightningBackend, LightningError
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Secret for HMAC-based invoice verification (mock mode)
|
# Read secret from centralised config (validated at startup in config.py)
|
||||||
_HMAC_SECRET_DEFAULT = "timmy-sovereign-sats"
|
_HMAC_SECRET = settings.l402_hmac_secret.encode()
|
||||||
_HMAC_SECRET_RAW = os.environ.get("L402_HMAC_SECRET", _HMAC_SECRET_DEFAULT)
|
|
||||||
_HMAC_SECRET = _HMAC_SECRET_RAW.encode()
|
|
||||||
|
|
||||||
if _HMAC_SECRET_RAW == _HMAC_SECRET_DEFAULT:
|
|
||||||
logger.warning(
|
|
||||||
"SEC: L402_HMAC_SECRET is using the default value — set a unique "
|
|
||||||
"secret in .env before deploying to production."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MockBackend(LightningBackend):
|
class MockBackend(LightningBackend):
|
||||||
|
|||||||
@@ -13,29 +13,20 @@ import base64
|
|||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from config import settings
|
||||||
from timmy_serve.payment_handler import payment_handler
|
from timmy_serve.payment_handler import payment_handler
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_MACAROON_SECRET_DEFAULT = "timmy-macaroon-secret"
|
# Read secrets from centralised config (validated at startup in config.py)
|
||||||
_MACAROON_SECRET_RAW = os.environ.get("L402_MACAROON_SECRET", _MACAROON_SECRET_DEFAULT)
|
_MACAROON_SECRET = settings.l402_macaroon_secret.encode()
|
||||||
_MACAROON_SECRET = _MACAROON_SECRET_RAW.encode()
|
_HMAC_SECRET_RAW = settings.l402_hmac_secret
|
||||||
|
|
||||||
_HMAC_SECRET_DEFAULT = "timmy-hmac-secret"
|
|
||||||
_HMAC_SECRET_RAW = os.environ.get("L402_HMAC_SECRET", _HMAC_SECRET_DEFAULT)
|
|
||||||
_HMAC_SECRET = _HMAC_SECRET_RAW.encode()
|
_HMAC_SECRET = _HMAC_SECRET_RAW.encode()
|
||||||
|
|
||||||
if _MACAROON_SECRET_RAW == _MACAROON_SECRET_DEFAULT or _HMAC_SECRET_RAW == _HMAC_SECRET_DEFAULT:
|
|
||||||
logger.warning(
|
|
||||||
"SEC: L402 secrets are using default values — set L402_MACAROON_SECRET "
|
|
||||||
"and L402_HMAC_SECRET in .env before deploying to production."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Macaroon:
|
class Macaroon:
|
||||||
|
|||||||
Reference in New Issue
Block a user