Fix CI timezone and fresh settings initialization
This commit is contained in:
+1
-1
@@ -129,7 +129,7 @@ def discover():
|
|||||||
def _validate_settings(payload: dict) -> None:
|
def _validate_settings(payload: dict) -> None:
|
||||||
try:
|
try:
|
||||||
ZoneInfo(payload.get("timezone") or "UTC")
|
ZoneInfo(payload.get("timezone") or "UTC")
|
||||||
except ZoneInfoNotFoundError as exc:
|
except (ValueError, ZoneInfoNotFoundError) as exc:
|
||||||
raise HTTPException(400, "Timezone must be a valid IANA timezone, for example Europe/Berlin or UTC") from exc
|
raise HTTPException(400, "Timezone must be a valid IANA timezone, for example Europe/Berlin or UTC") from exc
|
||||||
if payload.get("setup_complete"):
|
if payload.get("setup_complete"):
|
||||||
missing = [
|
missing = [
|
||||||
|
|||||||
@@ -1,14 +1,32 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
from .db import db, utc_now
|
from .db import db, utc_now
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_timezone(value: str) -> str:
|
||||||
|
timezone_name = value.strip()
|
||||||
|
if not timezone_name:
|
||||||
|
return "UTC"
|
||||||
|
if timezone_name.startswith("/"):
|
||||||
|
marker = "/zoneinfo/"
|
||||||
|
if marker in timezone_name:
|
||||||
|
timezone_name = timezone_name.split(marker, 1)[1]
|
||||||
|
else:
|
||||||
|
timezone_name = timezone_name.lstrip("/")
|
||||||
|
try:
|
||||||
|
ZoneInfo(timezone_name)
|
||||||
|
except (ValueError, ZoneInfoNotFoundError):
|
||||||
|
return "UTC"
|
||||||
|
return timezone_name
|
||||||
|
|
||||||
|
|
||||||
def _system_timezone() -> str:
|
def _system_timezone() -> str:
|
||||||
timezone_file = Path("/etc/timezone")
|
timezone_file = Path("/etc/timezone")
|
||||||
if timezone_file.exists():
|
if timezone_file.exists():
|
||||||
value = timezone_file.read_text().strip()
|
value = timezone_file.read_text().strip()
|
||||||
if value:
|
if value:
|
||||||
return value
|
return _normalize_timezone(value)
|
||||||
return "UTC"
|
return "UTC"
|
||||||
|
|
||||||
|
|
||||||
@@ -147,3 +165,17 @@ def run_migrations() -> None:
|
|||||||
"INSERT INTO schema_migrations(version, applied_at) VALUES (?, ?)",
|
"INSERT INTO schema_migrations(version, applied_at) VALUES (?, ?)",
|
||||||
(3, now),
|
(3, now),
|
||||||
)
|
)
|
||||||
|
if 4 not in applied:
|
||||||
|
now = utc_now()
|
||||||
|
row = conn.execute("SELECT value FROM settings WHERE key = 'timezone'").fetchone()
|
||||||
|
if row:
|
||||||
|
normalized_timezone = _normalize_timezone(row["value"])
|
||||||
|
if normalized_timezone != row["value"]:
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE settings SET value = ?, updated_at = ? WHERE key = 'timezone'",
|
||||||
|
(normalized_timezone, now),
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO schema_migrations(version, applied_at) VALUES (?, ?)",
|
||||||
|
(4, now),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
from .db import db, utc_now
|
from .db import db, utc_now
|
||||||
from .migrations import DEFAULT_SETTINGS
|
from .migrations import DEFAULT_SETTINGS, run_migrations
|
||||||
|
|
||||||
|
|
||||||
INT_KEYS = {"default_retention_value", "max_concurrent_backups"}
|
INT_KEYS = {"default_retention_value", "max_concurrent_backups"}
|
||||||
@@ -27,7 +29,18 @@ def serialize_value(key: str, value) -> str:
|
|||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_missing_settings_table(exc: sqlite3.OperationalError) -> bool:
|
||||||
|
return "no such table: settings" in str(exc).lower()
|
||||||
|
|
||||||
|
|
||||||
def get_settings() -> dict:
|
def get_settings() -> dict:
|
||||||
|
try:
|
||||||
|
with db() as conn:
|
||||||
|
rows = conn.execute("SELECT key, value FROM settings").fetchall()
|
||||||
|
except sqlite3.OperationalError as exc:
|
||||||
|
if not _is_missing_settings_table(exc):
|
||||||
|
raise
|
||||||
|
run_migrations()
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
rows = conn.execute("SELECT key, value FROM settings").fetchall()
|
rows = conn.execute("SELECT key, value FROM settings").fetchall()
|
||||||
raw = {row["key"]: row["value"] for row in rows}
|
raw = {row["key"]: row["value"] for row in rows}
|
||||||
@@ -42,6 +55,21 @@ def set_settings(values: dict) -> dict:
|
|||||||
unknown = set(values) - valid_keys
|
unknown = set(values) - valid_keys
|
||||||
if unknown:
|
if unknown:
|
||||||
raise ValueError(f"Unknown settings: {', '.join(sorted(unknown))}")
|
raise ValueError(f"Unknown settings: {', '.join(sorted(unknown))}")
|
||||||
|
try:
|
||||||
|
with db() as conn:
|
||||||
|
for key, value in values.items():
|
||||||
|
conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO settings(key, value, updated_at)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
||||||
|
""",
|
||||||
|
(key, serialize_value(key, value), now),
|
||||||
|
)
|
||||||
|
except sqlite3.OperationalError as exc:
|
||||||
|
if not _is_missing_settings_table(exc):
|
||||||
|
raise
|
||||||
|
run_migrations()
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
for key, value in values.items():
|
for key, value in values.items():
|
||||||
conn.execute(
|
conn.execute(
|
||||||
@@ -56,6 +84,13 @@ def set_settings(values: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def get_setting(key: str, default: str = "") -> str:
|
def get_setting(key: str, default: str = "") -> str:
|
||||||
|
try:
|
||||||
|
with db() as conn:
|
||||||
|
row = conn.execute("SELECT value FROM settings WHERE key = ?", (key,)).fetchone()
|
||||||
|
except sqlite3.OperationalError as exc:
|
||||||
|
if not _is_missing_settings_table(exc):
|
||||||
|
raise
|
||||||
|
run_migrations()
|
||||||
with db() as conn:
|
with db() as conn:
|
||||||
row = conn.execute("SELECT value FROM settings WHERE key = ?", (key,)).fetchone()
|
row = conn.execute("SELECT value FROM settings WHERE key = ?", (key,)).fetchone()
|
||||||
if row:
|
if row:
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
from app import commands
|
from app import commands
|
||||||
from app.main import _validate_settings
|
from app.main import _validate_settings
|
||||||
|
from app.migrations import _normalize_timezone
|
||||||
|
|
||||||
|
|
||||||
|
def test_system_timezone_absolute_utc_is_normalized():
|
||||||
|
assert _normalize_timezone("/UTC") == "UTC"
|
||||||
|
assert _normalize_timezone("/usr/share/zoneinfo/Europe/Berlin") == "Europe/Berlin"
|
||||||
|
|
||||||
|
|
||||||
def test_plain_onedrive_remote_is_accepted(monkeypatch):
|
def test_plain_onedrive_remote_is_accepted(monkeypatch):
|
||||||
|
|||||||
Reference in New Issue
Block a user