Fix DATABASE_URL corruption from special characters in POSTGRES_PASSWORD
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
# Bind directly to the Tailscale address in production.
|
||||
# All password/token values may contain special characters like @ : / # %.
|
||||
BIND_IP=100.103.83.12
|
||||
POSTGRES_DB=time_track
|
||||
POSTGRES_USER=time_track
|
||||
|
||||
@@ -16,6 +16,7 @@ A self-hosted, single-user combined timesheet server for `time_track` manual int
|
||||
cp .env.example .env
|
||||
# Generate three distinct high-entropy values for POSTGRES_PASSWORD, ADMIN_PASSWORD,
|
||||
# SYNC_API_TOKEN, and SESSION_SECRET. Keep .env private.
|
||||
# Values containing special characters (for example @ or :) are safe.
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
|
||||
+22
-2
@@ -1,10 +1,30 @@
|
||||
import os
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import URL
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
||||
|
||||
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///./time_track.db")
|
||||
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
||||
|
||||
def build_database_url() -> URL | str:
|
||||
"""Prefer DATABASE_URL; otherwise build a safely escaped URL from parts.
|
||||
|
||||
Passwords are passed as URL components (not string interpolation), so
|
||||
characters like '@', ':', '/', '#' and '%' cannot corrupt the hostname.
|
||||
"""
|
||||
database_url = os.environ.get("DATABASE_URL")
|
||||
if database_url:
|
||||
return database_url
|
||||
return URL.create(
|
||||
"postgresql+psycopg",
|
||||
username=os.environ.get("POSTGRES_USER", "time_track"),
|
||||
password=os.environ.get("POSTGRES_PASSWORD", ""),
|
||||
host=os.environ.get("POSTGRES_HOST", "localhost"),
|
||||
port=int(os.environ.get("POSTGRES_PORT", "5432")),
|
||||
database=os.environ.get("POSTGRES_DB", "time_track"),
|
||||
)
|
||||
|
||||
|
||||
engine = create_engine(build_database_url(), pool_pre_ping=True)
|
||||
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
|
||||
|
||||
|
||||
|
||||
+15
-1
@@ -1,6 +1,8 @@
|
||||
import logging
|
||||
import os
|
||||
import secrets
|
||||
import shutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
@@ -12,6 +14,7 @@ from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pypdf import PdfReader
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import OperationalError
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
@@ -34,7 +37,18 @@ templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
|
||||
def startup() -> None:
|
||||
if not ADMIN_PASSWORD or not SYNC_API_TOKEN:
|
||||
raise RuntimeError("ADMIN_PASSWORD and SYNC_API_TOKEN must be configured")
|
||||
Base.metadata.create_all(bind=engine)
|
||||
# Postgres may briefly restart or lag behind its health check at deploy time.
|
||||
for attempt in range(1, 11):
|
||||
try:
|
||||
Base.metadata.create_all(bind=engine)
|
||||
break
|
||||
except OperationalError as exc:
|
||||
if attempt == 10:
|
||||
raise
|
||||
logging.getLogger("uvicorn.error").warning(
|
||||
"Database not ready (attempt %s/10): %s", attempt, exc.orig
|
||||
)
|
||||
time.sleep(2)
|
||||
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
|
||||
+5
-1
@@ -17,7 +17,11 @@ services:
|
||||
app:
|
||||
build: .
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-time_track}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-time_track}
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_USER: ${POSTGRES_USER:-time_track}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-time_track}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?Set ADMIN_PASSWORD in .env}
|
||||
SYNC_API_TOKEN: ${SYNC_API_TOKEN:?Set SYNC_API_TOKEN in .env}
|
||||
SESSION_SECRET: ${SESSION_SECRET:?Set SESSION_SECRET in .env}
|
||||
|
||||
Reference in New Issue
Block a user