Use env defaults for admin seed credentials

This commit is contained in:
TronoSfera 2026-01-19 11:37:03 +03:00
parent e71bbd5b2e
commit 036502596a
5 changed files with 48 additions and 4 deletions

View file

@ -10,6 +10,12 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
SECRET_KEY=mysecretkey SECRET_KEY=mysecretkey
# Initial admin credentials (created on first startup if no users exist).
# If omitted, the server falls back to USERNAME/PASSWORD for backward
# compatibility with older compose files.
ADMIN_USERNAME=admin
ADMIN_PASSWORD=adminpass
# Use Postgres instead of SQLite. This DSN is used by SQLAlchemy. If you # Use Postgres instead of SQLite. This DSN is used by SQLAlchemy. If you
# leave it empty or omit it, the server will fall back to a local SQLite # leave it empty or omit it, the server will fall back to a local SQLite
# database stored in `/app/backup.db`. # database stored in `/app/backup.db`.
@ -62,4 +68,4 @@ BACKUP_INTERVAL=3600
# variables (SERVER_URL, USERNAME, PASSWORD and MONITORED_PATHS) to be # variables (SERVER_URL, USERNAME, PASSWORD and MONITORED_PATHS) to be
# provided. When `true` (default), the client starts a small web server on # provided. When `true` (default), the client starts a small web server on
# port 8080 to collect configuration interactively. # port 8080 to collect configuration interactively.
CLIENT_UI_ENABLED=true CLIENT_UI_ENABLED=true

View file

@ -133,6 +133,10 @@ by supplying a `.env` file, or via the builtin web interface on port 8080.
browser, authenticate using a token from `/api/login`, and use the “Create browser, authenticate using a token from `/api/login`, and use the “Create
User” form. Alternatively, you can call the `/api/register_user` endpoint User” form. Alternatively, you can call the `/api/register_user` endpoint
directly using a bearer token from an existing admin. directly using a bearer token from an existing admin.
* On first startup, the server will create an initial admin user if the
database is empty. Configure `ADMIN_USERNAME` and `ADMIN_PASSWORD` (or
`USERNAME`/`PASSWORD` for backward compatibility) in your environment or
`.env` file to control these credentials.
* Ensure that the retention policies set on each user reflect your backup * Ensure that the retention policies set on each user reflect your backup
strategy. For example, specifying `retention_versions=5` keeps the five strategy. For example, specifying `retention_versions=5` keeps the five
most recent versions of each file; specifying `retention_days=30` retains most recent versions of each file; specifying `retention_days=30` retains
@ -157,4 +161,4 @@ that could be improved include:
Despite these limitations, the provided code demonstrates the core Despite these limitations, the provided code demonstrates the core
functionality required for a secure, deduplicated backup service and functionality required for a secure, deduplicated backup service and
provides a foundation for further development. provides a foundation for further development.

View file

@ -9,6 +9,9 @@ services:
environment: environment:
# Change SECRET_KEY in production # Change SECRET_KEY in production
SECRET_KEY: "mysecretkey" SECRET_KEY: "mysecretkey"
# Initial admin user (created on first startup if no users exist)
ADMIN_USERNAME: "${ADMIN_USERNAME:-admin}"
ADMIN_PASSWORD: "${ADMIN_PASSWORD:-adminpass}"
# Use Postgres instead of SQLite. The DATABASE_URL uses the same # Use Postgres instead of SQLite. The DATABASE_URL uses the same
# credentials defined in the db service below. # credentials defined in the db service below.
DATABASE_URL: "postgresql+psycopg2://backup:backup@db:5432/backup" DATABASE_URL: "postgresql+psycopg2://backup:backup@db:5432/backup"
@ -67,4 +70,4 @@ volumes:
server_data: server_data:
server_db: server_db:
postgres_data: postgres_data:
minio_data: minio_data:

View file

@ -9,6 +9,9 @@ services:
environment: environment:
# Change SECRET_KEY in production # Change SECRET_KEY in production
SECRET_KEY: "mysecretkey" SECRET_KEY: "mysecretkey"
# Initial admin user (created on first startup if no users exist)
ADMIN_USERNAME: "${ADMIN_USERNAME:-admin}"
ADMIN_PASSWORD: "${ADMIN_PASSWORD:-adminpass}"
# Use Postgres instead of SQLite. The DATABASE_URL uses the same # Use Postgres instead of SQLite. The DATABASE_URL uses the same
# credentials defined in the db service below. # credentials defined in the db service below.
DATABASE_URL: "postgresql+psycopg2://backup:backup@db:5432/backup" DATABASE_URL: "postgresql+psycopg2://backup:backup@db:5432/backup"
@ -97,4 +100,4 @@ volumes:
server_db: server_db:
client_data: client_data:
postgres_data: postgres_data:
minio_data: minio_data:

View file

@ -51,6 +51,32 @@ templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "t
storage = storage_module.get_storage() storage = storage_module.get_storage()
def ensure_default_admin() -> None:
"""Seed an initial admin user when the database is empty.
Uses ADMIN_USERNAME/ADMIN_PASSWORD, falling back to USERNAME/PASSWORD for
backward compatibility with existing compose files and .env settings.
"""
username = os.getenv("ADMIN_USERNAME") or os.getenv("USERNAME")
password = os.getenv("ADMIN_PASSWORD") or os.getenv("PASSWORD")
if not username or not password:
return
db = database.SessionLocal()
try:
existing_user = db.query(models.User).first()
if existing_user:
return
admin = models.User(
username=username,
hashed_password=auth.hash_password(password),
is_admin=True,
)
db.add(admin)
db.commit()
finally:
db.close()
@app.exception_handler(HTTPException) @app.exception_handler(HTTPException)
async def custom_http_exception_handler(request: Request, exc: HTTPException) -> Response: async def custom_http_exception_handler(request: Request, exc: HTTPException) -> Response:
if ( if (
@ -77,6 +103,8 @@ async def on_startup() -> None:
# Column already exists or migration failed; ignore # Column already exists or migration failed; ignore
pass pass
ensure_default_admin()
@app.post("/api/register_user", response_model=schemas.UserOut) @app.post("/api/register_user", response_model=schemas.UserOut)
async def register_user( async def register_user(