diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 199c3d7..d6e0b2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,9 @@ name: Release on: push: - branches: [main, "feature/**"] + branches: + - main + - "**" # build :dev- image for any branch push (e.g. feature/x, zs/edit, fix/y) permissions: contents: write diff --git a/app/main.py b/app/main.py index 504cb16..1753343 100644 --- a/app/main.py +++ b/app/main.py @@ -90,7 +90,10 @@ def create_app() -> FastAPI: SCHEMA_VERSION, ) - # Seed admin user for testing/CI (when SEED_ADMIN_EMAIL is set) + # Seed admin user for testing/CI (when SEED_ADMIN_EMAIL is set). + # Optional: SEED_ADMIN_PASSWORD sets password_hash on first seed so the user + # can log in immediately without bootstrap. Only applied if the user has no + # password_hash yet — never overwrites an existing password. seed_email = os.environ.get("SEED_ADMIN_EMAIL") if seed_email: try: @@ -98,10 +101,25 @@ def create_app() -> FastAPI: from src.repositories.users import UserRepository conn = get_system_db() repo = UserRepository(conn) - if not repo.get_by_email(seed_email): + seed_password = os.environ.get("SEED_ADMIN_PASSWORD") or None + password_hash = None + if seed_password: + from argon2 import PasswordHasher + password_hash = PasswordHasher().hash(seed_password) + existing = repo.get_by_email(seed_email) + if not existing: import uuid - repo.create(id=str(uuid.uuid4()), email=seed_email, name="Admin", role="admin") - logger.info("Seeded admin user: %s", seed_email) + repo.create( + id=str(uuid.uuid4()), + email=seed_email, + name="Admin", + role="admin", + password_hash=password_hash, + ) + logger.info("Seeded admin user: %s (password=%s)", seed_email, "yes" if password_hash else "no") + elif password_hash and not existing.get("password_hash"): + repo.update(id=existing["id"], password_hash=password_hash, role="admin") + logger.info("Set password on existing seed admin: %s", seed_email) conn.close() except Exception as e: logger.warning(f"Could not seed admin: {e}") diff --git a/config/.env.template b/config/.env.template index bd0de14..66122e0 100644 --- a/config/.env.template +++ b/config/.env.template @@ -21,6 +21,8 @@ SESSION_SECRET= # python -c "import secrets; print(secrets.token_he # ── BOOTSTRAP (first deploy only) ─────────────────── # SEED_ADMIN_EMAIL=admin@example.com +# SEED_ADMIN_PASSWORD= # Dev helper only — sets password_hash on seed. +# # Never overwrites an existing password. # ── EMAIL / SMTP (required for magic link auth) ───── # SMTP_HOST=smtp.gmail.com diff --git a/infra/modules/customer-instance/main.tf b/infra/modules/customer-instance/main.tf index b8cf4cd..3666ad3 100644 --- a/infra/modules/customer-instance/main.tf +++ b/infra/modules/customer-instance/main.tf @@ -209,18 +209,19 @@ resource "google_compute_instance" "vm" { } metadata_startup_script = templatefile("${path.module}/startup-script.sh.tpl", { - customer_name = var.customer_name - image_repo = var.image_repo - image_tag = each.value.image_tag - upgrade_mode = each.value.upgrade_mode - tls_mode = each.value.tls_mode - domain = each.value.domain - acme_email = var.acme_email != "" ? var.acme_email : var.seed_admin_email - data_source = var.data_source - keboola_stack_url = var.keboola_stack_url - seed_admin_email = var.seed_admin_email - role = each.value.role - compose_ref = var.compose_ref + customer_name = var.customer_name + image_repo = var.image_repo + image_tag = each.value.image_tag + upgrade_mode = each.value.upgrade_mode + tls_mode = each.value.tls_mode + domain = each.value.domain + acme_email = var.acme_email != "" ? var.acme_email : var.seed_admin_email + data_source = var.data_source + keboola_stack_url = var.keboola_stack_url + seed_admin_email = var.seed_admin_email + seed_admin_password = var.enable_seed_password ? var.seed_admin_password : "" + role = each.value.role + compose_ref = var.compose_ref }) service_account { @@ -289,8 +290,8 @@ resource "google_monitoring_alert_policy" "health_failure" { conditions { display_name = "Uptime check failed > 5 min" condition_threshold { - filter = "metric.type=\"monitoring.googleapis.com/uptime_check/check_passed\" AND metric.labels.check_id=\"${google_monitoring_uptime_check_config.health[each.key].uptime_check_id}\" AND resource.type=\"uptime_url\"" - duration = "300s" + filter = "metric.type=\"monitoring.googleapis.com/uptime_check/check_passed\" AND metric.labels.check_id=\"${google_monitoring_uptime_check_config.health[each.key].uptime_check_id}\" AND resource.type=\"uptime_url\"" + duration = "300s" # ALIGN_FRACTION_TRUE yields fraction of checks that returned true. # If the fraction stays < 1 (i.e. any probe failed) for 5 min → alert. comparison = "COMPARISON_LT" diff --git a/infra/modules/customer-instance/startup-script.sh.tpl b/infra/modules/customer-instance/startup-script.sh.tpl index ed444ab..9d6b740 100644 --- a/infra/modules/customer-instance/startup-script.sh.tpl +++ b/infra/modules/customer-instance/startup-script.sh.tpl @@ -15,6 +15,7 @@ ACME_EMAIL="${acme_email}" DATA_SOURCE="${data_source}" KEBOOLA_STACK_URL="${keboola_stack_url}" SEED_ADMIN_EMAIL="${seed_admin_email}" +SEED_ADMIN_PASSWORD="${seed_admin_password}" ROLE="${role}" COMPOSE_REF="${compose_ref}" @@ -81,6 +82,7 @@ DATA_SOURCE=$DATA_SOURCE KEBOOLA_STORAGE_TOKEN=$KEBOOLA_TOKEN KEBOOLA_STACK_URL=$KEBOOLA_STACK_URL SEED_ADMIN_EMAIL=$SEED_ADMIN_EMAIL +SEED_ADMIN_PASSWORD=$SEED_ADMIN_PASSWORD LOG_LEVEL=info DOMAIN=$DOMAIN AGNES_TAG=$IMAGE_TAG diff --git a/infra/modules/customer-instance/variables.tf b/infra/modules/customer-instance/variables.tf index a4f9194..9543f91 100644 --- a/infra/modules/customer-instance/variables.tf +++ b/infra/modules/customer-instance/variables.tf @@ -53,6 +53,19 @@ variable "seed_admin_email" { type = string } +variable "enable_seed_password" { + description = "Pokud true, seed admin user dostane hned password_hash ze seed_admin_password (dev helper). Ponech false v prod — admin si heslo nastaví přes /auth/bootstrap nebo Google OAuth." + type = bool + default = false +} + +variable "seed_admin_password" { + description = "Plain-text heslo pro seed admina. Použije se jen když enable_seed_password=true. POZOR: ukládá se do Terraform state." + type = string + default = "" + sensitive = true +} + variable "data_source" { description = "Typ data source — keboola | bigquery | csv" type = string