diff --git a/.env.example b/.env.example index 5d1dc6a..28746c6 100644 --- a/.env.example +++ b/.env.example @@ -8,9 +8,19 @@ SESSION_SECRET=change-me-to-a-random-string # Admin password hash (argon2). Generate with: # python -c "from passlib.hash import argon2; print(argon2.hash('mysecret'))" -# If unset and no admin stored in Redis, the app shows a first-run setup page. +# If unset and no admin stored in Redis, the app shows a first-run setup page +# (recommended — the wizard stores the hash in Redis and avoids the $-escaping trap below). ADMIN_USERNAME=admin # ADMIN_PASSWORD_HASH= +# +# IMPORTANT: argon2 hashes start with `$argon2id$v=19$m=...` and contain many `$` +# characters. Docker Compose interprets `$word` as variable substitution, so pasting +# a raw hash here will produce warnings like +# WARN The "argon2id" variable is not set. Defaulting to a blank string. +# and the container will receive a mangled hash (and you will not be able to log in). +# Quoting (single or double) does NOT escape `$` in .env — you must double every `$`: +# ADMIN_PASSWORD_HASH=$$argon2id$$v=19$$m=65536,t=3,p=4$$$$ +# Or just leave this unset and use the /setup wizard on first boot. # --- Redis --- REDIS_URL=redis://redis:6379/0 diff --git a/app/config.py b/app/config.py index 61b5da2..76e3f7b 100644 --- a/app/config.py +++ b/app/config.py @@ -11,7 +11,7 @@ from .settings import settings CONFIG_PATH = Path(settings.ups_config_path) # legacy path for migration -_NAME_RE = re.compile(r"^[a-zA-Z0-9_-]{1,32}$") +_NAME_RE = re.compile(r"^[a-zA-Z0-9_.\- ]{1,32}$") _HOSTNAME_RE = re.compile( r"^(?=.{1,253}$)([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" ) @@ -78,9 +78,9 @@ class UPSConfig(BaseModel): def _validate_name(cls, v: str) -> str: if not _NAME_RE.match(v): raise ValueError( - "name must be 1-32 chars, alphanumeric / underscore / dash only" + "name must be 1-32 chars (letters, digits, space, dot, underscore, dash)" ) - return v + return v.strip() @field_validator("host") @classmethod diff --git a/tests/test_validators.py b/tests/test_validators.py index 4cab602..b56268b 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -57,8 +57,8 @@ def test_reject_name_too_long(): def test_reject_bad_name_chars(): - with pytest.raises(Exception, match="alphanumeric"): - UPSConfig(name="bad name!", host="192.168.1.10") + with pytest.raises(Exception, match="letters, digits"): + UPSConfig(name="bad/name!", host="192.168.1.10") def test_accept_valid_name_chars(): @@ -66,6 +66,13 @@ def test_accept_valid_name_chars(): assert u.name == "ups_rack-01" +def test_accept_name_with_space_and_dot(): + u = UPSConfig(name="APC UPS", host="192.168.1.10") + assert u.name == "APC UPS" + u2 = UPSConfig(name="Server.Room.UPS", host="192.168.1.10") + assert u2.name == "Server.Room.UPS" + + def test_reject_invalid_port(): with pytest.raises(Exception): UPSConfig(name="x", host="192.168.1.10", port=0)