Files
homelable/.env.example
T
Pouzor 6b11e7ed34 fix(config): unwrap quoted secrets that podman-compose passes through
bcrypt hashes and most OIDC client secrets contain `$`, so `.env.example`
tells operators to single-quote them or Docker Compose eats the `$`. Docker
Compose then strips those quotes itself when reading `env_file`, and so does
python-dotenv on the `env_file=` path — but podman-compose (1.0.6) forwards
them verbatim. The container receives `'$2b$12$...'`, quotes included, every
bcrypt verify fails, and the logged error blames the operator's hash.

Unwrap one matched pair of surrounding quotes from SECRET_KEY,
AUTH_PASSWORD_HASH and OIDC_CLIENT_SECRET before validating them, so the same
.env works under either container runtime. Only a matched pair is removed: a
value with a single stray quote still reaches the existing validation instead
of being silently patched up, and SECRET_KEY is unwrapped before the 32-byte
OIDC floor is checked so quotes cannot pad a short key past it.

Fixes #410

ha-relevant: no
2026-09-04 13:40:25 +02:00

116 lines
5.7 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backend - server-side only (NEVER commit .env)
# Generate: python3 -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=change_me_in_production
SQLITE_PATH=./data/homelab.db
# Uploaded media (floor plans) folder. Optional — defaults to <SQLITE_PATH dir>/uploads,
# which sits on the same persistent volume as the DB.
# UPLOAD_DIR=./data/uploads
# Set this to the URL(s) you use to access Homelable in your browser.
CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"]
# Serve the UI under a subpath instead of the root of the origin, e.g. /homelab/.
# Read by docker-compose at *build* time (docker compose build frontend), since the
# prefix is baked into the frontend bundle. See INSTALLATION.md.
# VITE_BASE_PATH=/homelab/
# Auth — local mode is the backward-compatible default.
AUTH_MODE=local
# Local credentials: admin / admin by default.
# ⚠️ Change before exposing on a network.
# Generate a new hash: python3 -c "import bcrypt; print(bcrypt.hashpw(b'yourpassword', bcrypt.gensalt()).decode())"
# ⚠️ Keep the single quotes around the hash — bcrypt hashes contain \$ which Docker misinterprets without them.
# podman-compose does not strip those quotes the way Docker Compose does, so the
# backend unwraps one matched pair itself — quoted or not, both work everywhere.
AUTH_USERNAME=admin
AUTH_PASSWORD_HASH='$2b$12$RtMbyw17l4N5UGzeXMNAWuzCaVV.XFBY7ZetWheQhxcBDcxahapkG'
# OpenID Connect (optional alternative to local auth).
# Set AUTH_MODE=oidc and configure every value below. The callback must be
# registered exactly at the identity provider. Keep CORS_ORIGINS restricted to
# the Homelable browser origin; wildcard CORS is rejected in OIDC mode. OIDC
# also requires SECRET_KEY to contain at least 32 bytes.
# AUTH_MODE=oidc
# OIDC_DISCOVERY_URL=https://idp.example/application/o/homelable/.well-known/openid-configuration
# OIDC_CLIENT_ID=homelable
# OIDC_CLIENT_SECRET=replace-with-a-secret
# OIDC_REDIRECT_URI=https://homelable.example/api/v1/auth/oidc/callback
# OIDC_SCOPES="openid profile email"
# OIDC_COOKIE_SECURE=true
# OIDC_SESSION_EXPIRE_MINUTES=480
# OIDC_TRANSACTION_EXPIRE_SECONDS=600
# Scanner — JSON array of CIDR ranges to scan
SCANNER_RANGES=["192.168.1.0/24"]
# Deep scan (optional) — extra nmap port ranges + HTTP probe for service ID on
# custom ports. Defaults below are overridable per-scan from the scan dialog.
# SCANNER_HTTP_RANGES: JSON array of port specs, each a single port "N" or an
# inclusive range "N-M" (1–65535, N <= M). Not CIDRs, not bare ints.
# Example: SCANNER_HTTP_RANGES=["8080","9000-9100"]
SCANNER_HTTP_RANGES=[]
SCANNER_HTTP_PROBE_ENABLED=false
SCANNER_HTTP_VERIFY_TLS=false
# Status checker interval in seconds
STATUS_CHECKER_INTERVAL=60
# MCP server — used by the mcp service (port 8001)
# MCP_API_KEY: authenticates AI clients (Claude Code, etc.) → MCP server
# MCP_SERVICE_KEY: authenticates MCP server → backend (never exposed externally)
# Generate keys: python3 -c "import secrets; print(secrets.token_hex(32))"
MCP_API_KEY=mcp_sk_changeme
MCP_SERVICE_KEY=svc_changeme
# Live view — read-only public canvas at /view?key=<value>
# Off by default. Set to a random secret to enable.
# Generate: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# LIVEVIEW_KEY=
# Gethomepage widget — read-only stats at /api/v1/stats/summary
# Off by default. Set to a random secret to enable; clients must send
# the same value in the `X-API-Key` header.
# Generate: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# HOMEPAGE_API_KEY=
# Proxmox VE import — pull hosts/VMs/LXC from the Proxmox REST API.
# The token is a credential: kept in memory only, never written to disk by the
# app, never returned by any API. Create it under Datacenter → Permissions →
# API Tokens and grant the read-only PVEAuditor role at path "/".
# Token id format: user@realm!tokenname (e.g. root@pam!homelable).
# Required only for auto-sync; one-off imports can pass the token in the dialog.
# PROXMOX_TOKEN_ID=root@pam!homelable
# PROXMOX_TOKEN_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# PROXMOX_HOST=192.168.1.10
# PROXMOX_PORT=8006
# PROXMOX_VERIFY_TLS=true # set false only for self-signed certs
# Zigbee2MQTT auto-sync — pull the mesh from an MQTT broker on a schedule.
# MQTT credentials are secrets: kept in memory only, never written to disk by
# the app, never returned by any API. Manual imports (the Zigbee dialog) are
# unaffected — this block only powers Settings → Zigbee auto-sync and /sync-now.
# Required only for auto-sync; one-off imports pass their config in the dialog.
# ZIGBEE_MQTT_HOST=192.168.1.20
# ZIGBEE_MQTT_PORT=1883
# ZIGBEE_MQTT_USERNAME=mqttuser # optional
# ZIGBEE_MQTT_PASSWORD=mqttpass # optional
# ZIGBEE_BASE_TOPIC=zigbee2mqtt
# ZIGBEE_MQTT_TLS=false # true for TLS brokers (typically port 8883)
# ZIGBEE_MQTT_TLS_INSECURE=false # skip cert verify (self-signed only; requires TLS)
# Seconds to wait for the Z2M bridge to answer a networkmap request. Applies to
# manual imports too. Raise it on a large mesh (200+ devices can take minutes)
# if an import fails with "Timed out waiting for networkmap response".
# ZIGBEE_NETWORKMAP_TIMEOUT=300
# Z-Wave JS UI (zwavejs2mqtt) auto-sync — same MQTT secret/env rules as Zigbee.
# ZWAVE_MQTT_HOST=192.168.1.20
# ZWAVE_MQTT_PORT=1883
# ZWAVE_MQTT_USERNAME=mqttuser # optional
# ZWAVE_MQTT_PASSWORD=mqttpass # optional
# ZWAVE_PREFIX=zwave
# ZWAVE_GATEWAY_NAME=zwavejs2mqtt
# ZWAVE_MQTT_TLS=false # true for TLS brokers (typically port 8883)
# ZWAVE_MQTT_TLS_INSECURE=false # skip cert verify (self-signed only; requires TLS)
# Seconds to wait for the gateway to answer an MQTT request. Same rationale as
# ZIGBEE_NETWORKMAP_TIMEOUT above.
# MQTT_RESPONSE_TIMEOUT=300