refactor(config): .env in config.yaml integriert

- auth.secret_key und smtp.* direkt in config/config.yaml aufgenommen
- config/env.example entfernt, config/config.example.yaml als Vorlage hinzugefügt
- meshbot/auth.py: config.env() → config.get() für alle Auth/SMTP-Werte
- meshbot/config.py: ENV_PATH, _load_env(), env() entfernt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-20 21:47:56 +01:00
parent 4b9dd45f05
commit 261f0dac13
7 changed files with 64 additions and 38 deletions

View file

@ -74,7 +74,7 @@
- Bot start: `/home/peter/meshdd-bot/venv/bin/python main.py`
- Web port: 8081 (konfigurierbar via `web.port`)
- Forgejo remote with token in URL
- Current version: 0.08.15
- Current version: 0.08.16
- Protobuf objects converted via `google.protobuf.json_format.MessageToDict()`
- Auth: bcrypt (12 rounds), aiohttp-session EncryptedCookieStorage, aiosmtplib for emails
- SMTP fallback: if no smtp.host configured, verification links logged to console

View file

@ -1,5 +1,14 @@
# Changelog
## [0.08.17] - 2026-02-20
### Changed
- **`.env`-Datei abgeschafft**: Alle Konfigurationswerte (`auth.secret_key`, `smtp.*`)
sind jetzt direkt in `config/config.yaml` enthalten keine separate `.env`-Datei mehr nötig.
- **`meshbot/auth.py`**: `config.env()` durch `config.get()` ersetzt (liest jetzt aus YAML).
- **`meshbot/config.py`**: `ENV_PATH`, `_load_env()` und `env()` entfernt.
- **`config/env.example`** entfernt.
## [0.08.16] - 2026-02-20
### Changed

View file

@ -0,0 +1,35 @@
version: "0.08.17"
bot:
name: "MeshDD-Bot"
command_prefix: "?"
meshtastic:
host: "192.168.11.4"
port: 4403
web:
host: "0.0.0.0"
port: 8081
online_threshold: 900
database:
path: "meshdd.db"
auth:
session_max_age: 86400
secret_key: "change-this-secret-key-32bytes!!"
smtp:
host: ""
port: 465
user: ""
password: ""
from: "MeshDD-Bot <noreply@example.com>"
app_url: "http://localhost:8081"
links:
- url: "https://meshtastic.org"
label: "Meshtastic"
- url: "https://meshmap.net"
label: "MeshMap"

View file

@ -1,4 +1,4 @@
version: "0.08.16"
version: "0.08.17"
bot:
name: "MeshDD-Bot"
@ -18,6 +18,15 @@ database:
auth:
session_max_age: 86400
secret_key: "4pY1h4S/pqpTojjPb7LV6TfaTZC8jas/DV3vixXIP8s="
smtp:
host: "ssl0.ovh.net"
port: 465
user: "info@meshdresden.eu"
password: "meshDresden"
from: "MeshDD-Bot <info@meshdresden.eu>"
app_url: "http://bot.home.pfeiffer-privat.de"
links:
- url: "https://meshtastic.org"

View file

@ -1,7 +0,0 @@
AUTH_SECRET_KEY=change-this-secret-key-32bytes!!
SMTP_HOST=
SMTP_PORT=465
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=MeshDD-Bot <noreply@example.com>
SMTP_APP_URL=http://localhost:8080

View file

@ -27,7 +27,7 @@ def check_password(password: str, hashed: str) -> bool:
# ── Session setup ────────────────────────────────────
def setup_session(app: web.Application):
secret_key = config.env("AUTH_SECRET_KEY", "change-this-secret-key-32bytes!!")
secret_key = config.get("auth.secret_key", "change-this-secret-key-32bytes!!")
# EncryptedCookieStorage accepts a Fernet object directly
key_bytes = secret_key.encode("utf-8")[:32].ljust(32, b"\0")
fernet_key = Fernet(base64.urlsafe_b64encode(key_bytes))
@ -88,7 +88,7 @@ def require_admin_api(request: web.Request):
# ── Email sending ────────────────────────────────────
async def _send_email(db, recipient: str, subject: str, html_body: str):
smtp_host = config.env("SMTP_HOST")
smtp_host = config.get("smtp.host", "")
if not smtp_host:
logger.info("SMTP not configured - email to %s not sent", recipient)
await db.log_email(recipient, subject, "console", "SMTP not configured")
@ -100,22 +100,21 @@ async def _send_email(db, recipient: str, subject: str, html_body: str):
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = config.env("SMTP_FROM", "MeshDD-Bot <noreply@example.com>")
msg["From"] = config.get("smtp.from", "MeshDD-Bot <noreply@example.com>")
msg["To"] = recipient
msg.set_content("Bitte HTML-fähigen E-Mail-Client verwenden.")
msg.add_alternative(html_body, subtype="html")
smtp_port = int(config.env("SMTP_PORT", "465"))
smtp_client = aiosmtplib.SMTP(
hostname=smtp_host,
port=smtp_port,
port=config.get("smtp.port", 465),
use_tls=True,
)
async with smtp_client:
await smtp_client.login(
config.env("SMTP_USER"),
config.env("SMTP_PASSWORD"),
config.get("smtp.user", ""),
config.get("smtp.password", ""),
)
await smtp_client.send_message(msg)
@ -127,7 +126,7 @@ async def _send_email(db, recipient: str, subject: str, html_body: str):
async def send_verification_email(db, email: str, token: str):
app_url = config.env("SMTP_APP_URL", "http://localhost:8080")
app_url = config.get("smtp.app_url", "http://localhost:8081")
verify_url = f"{app_url}/auth/verify?token={token}"
logger.info("Verification link for %s: %s", email, verify_url)
subject = "MeshDD-Bot - E-Mail verifizieren"
@ -142,7 +141,7 @@ async def send_verification_email(db, email: str, token: str):
async def send_reset_email(db, email: str, token: str):
app_url = config.env("SMTP_APP_URL", "http://localhost:8080")
app_url = config.get("smtp.app_url", "http://localhost:8081")
reset_url = f"{app_url}/auth/reset-password?token={token}"
logger.info("Reset link for %s: %s", email, reset_url)
subject = "MeshDD-Bot - Passwort zuruecksetzen"
@ -157,7 +156,7 @@ async def send_reset_email(db, email: str, token: str):
async def send_user_info_email(db, email: str, name: str, password: str = None):
app_url = config.env("SMTP_APP_URL", "http://localhost:8080")
app_url = config.get("smtp.app_url", "http://localhost:8081")
login_url = f"{app_url}/login"
subject = "MeshDD-Bot - Deine Zugangsdaten"
pw_line = f"<p><strong>Passwort:</strong> {password}</p>" if password else "<p>Dein Passwort wurde nicht geaendert.</p>"

View file

@ -7,26 +7,12 @@ import yaml
logger = logging.getLogger(__name__)
CONFIG_PATH = os.environ.get("CONFIG_PATH", os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "config.yaml"))
ENV_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", ".env")
_config = {}
_mtime = 0.0
_callbacks = []
def _load_env():
if not os.path.exists(ENV_PATH):
return
with open(ENV_PATH, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
os.environ.setdefault(key.strip(), value.strip())
logger.info("Environment loaded from %s", ENV_PATH)
def _load():
global _config, _mtime
with open(CONFIG_PATH, "r") as f:
@ -75,10 +61,5 @@ def get(key: str, default=None):
return val
def env(key: str, default: str = "") -> str:
return os.environ.get(key, default)
# Load on import
_load_env()
_load()