From 036502596a52131bc514ca1cb1498760d9cbaa58 Mon Sep 17 00:00:00 2001 From: TronoSfera <119615520+TronoSfera@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:37:03 +0300 Subject: [PATCH] Use env defaults for admin seed credentials --- .env.example | 8 +++++++- README.md | 6 +++++- docker-compose.server.yml | 5 ++++- docker-compose.yml | 5 ++++- server/main.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 652dc84..5b832dc 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,12 @@ # ----------------------------------------------------------------------------- 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 # leave it empty or omit it, the server will fall back to a local SQLite # database stored in `/app/backup.db`. @@ -62,4 +68,4 @@ BACKUP_INTERVAL=3600 # variables (SERVER_URL, USERNAME, PASSWORD and MONITORED_PATHS) to be # provided. When `true` (default), the client starts a small web server on # port 8080 to collect configuration interactively. -CLIENT_UI_ENABLED=true \ No newline at end of file +CLIENT_UI_ENABLED=true diff --git a/README.md b/README.md index 73b405d..e99f1a9 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,10 @@ by supplying a `.env` file, or via the built‑in web interface on port 8080. browser, authenticate using a token from `/api/login`, and use the “Create User” form. Alternatively, you can call the `/api/register_user` endpoint 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 strategy. For example, specifying `retention_versions=5` keeps the five 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 functionality required for a secure, deduplicated backup service and -provides a foundation for further development. \ No newline at end of file +provides a foundation for further development. diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 51d2981..ed10854 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -9,6 +9,9 @@ services: environment: # Change SECRET_KEY in production 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 # credentials defined in the db service below. DATABASE_URL: "postgresql+psycopg2://backup:backup@db:5432/backup" @@ -67,4 +70,4 @@ volumes: server_data: server_db: postgres_data: - minio_data: \ No newline at end of file + minio_data: diff --git a/docker-compose.yml b/docker-compose.yml index fe072e0..3f6d66a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,9 @@ services: environment: # Change SECRET_KEY in production 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 # credentials defined in the db service below. DATABASE_URL: "postgresql+psycopg2://backup:backup@db:5432/backup" @@ -97,4 +100,4 @@ volumes: server_db: client_data: postgres_data: - minio_data: \ No newline at end of file + minio_data: diff --git a/server/main.py b/server/main.py index fba077f..f2fc34f 100644 --- a/server/main.py +++ b/server/main.py @@ -51,6 +51,32 @@ templates = Jinja2Templates(directory=os.path.join(os.path.dirname(__file__), "t 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) async def custom_http_exception_handler(request: Request, exc: HTTPException) -> Response: if ( @@ -77,6 +103,8 @@ async def on_startup() -> None: # Column already exists or migration failed; ignore pass + ensure_default_admin() + @app.post("/api/register_user", response_model=schemas.UserOut) async def register_user(