47a183aa65
- Added WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL environment variable to promote a specified user to system admin on startup. - Introduced a new bootstrap process in `bootstrap.go` to handle the promotion logic. - Updated `.env.example` to document the new environment variable and its behavior. - Created new views for managing system administrators and system settings, including listing, promoting, and revoking admin privileges. - Enhanced the frontend to reflect the new system admin features, including UI elements for admin management and settings configuration. - Updated API interfaces to support system admin functionalities, ensuring proper data handling and user management.
97 lines
3.7 KiB
Go
97 lines
3.7 KiB
Go
// Bootstrap-time hooks that run after the DI container is built but
|
|
// before the HTTP server starts listening. These are deliberately
|
|
// best-effort: any failure here only warns and does NOT abort startup.
|
|
// The reasoning is that an operator running with a misconfigured env
|
|
// var should still be able to bring the server up (and fix the issue
|
|
// from the running instance) rather than have a typo brick the deploy.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
|
|
"go.uber.org/dig"
|
|
|
|
"github.com/Tencent/WeKnora/internal/logger"
|
|
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
|
)
|
|
|
|
// bootstrapEnvVar is the env var that names the email of the user who
|
|
// should be promoted to system administrator on every startup.
|
|
//
|
|
// Why an env var (vs a CLI subcommand)?
|
|
// - Zero-friction in docker-compose / k8s deploys: set it once in the
|
|
// manifest and the very first user account that signs up with that
|
|
// email is auto-promoted, with no extra ops step.
|
|
// - Idempotent: if the user is already a system admin, bootstrapping is
|
|
// a no-op (no DB write, no log noise beyond a debug line).
|
|
// - Safe to leave set: the operation only ever PROMOTES; it never
|
|
// demotes. So leaving the var in place across restarts won't undo
|
|
// manual revokes from the UI.
|
|
const bootstrapEnvVar = "WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL"
|
|
|
|
// runStartupBootstrap consults the env and applies any one-shot
|
|
// bootstrap actions. Currently it only handles system-admin promotion;
|
|
// future bootstrap steps (default model seeding, etc.) can be added
|
|
// here as additional dig.Invoke calls.
|
|
func runStartupBootstrap(c *dig.Container) {
|
|
ctx := context.Background()
|
|
email := strings.TrimSpace(os.Getenv(bootstrapEnvVar))
|
|
if email == "" {
|
|
return
|
|
}
|
|
// dig.Invoke resolves UserService from the container; if user
|
|
// service registration is broken we want to know loudly, but still
|
|
// not abort startup — bootstrap is best-effort.
|
|
if err := c.Invoke(func(userSvc interfaces.UserService) {
|
|
bootstrapSystemAdmin(ctx, userSvc, email)
|
|
}); err != nil {
|
|
logger.Warnf(ctx, "[bootstrap] failed to resolve UserService: %v", err)
|
|
}
|
|
}
|
|
|
|
// bootstrapSystemAdmin promotes the user identified by `email` to system
|
|
// administrator if they exist and are not already one. The function is
|
|
// idempotent and non-fatal — it warns and returns on every error path.
|
|
//
|
|
// The bootstrap intentionally does NOT create a user when the email is
|
|
// not yet registered: account creation is a workflow with side effects
|
|
// (password hashing, tenant assignment, audit) that we don't want to
|
|
// short-circuit. Operators should sign up normally first, then set the
|
|
// env var on the next restart.
|
|
func bootstrapSystemAdmin(ctx context.Context, userSvc interfaces.UserService, email string) {
|
|
user, err := userSvc.GetUserByEmail(ctx, email)
|
|
if err != nil {
|
|
// "not found" surfaces as an error in this codebase; treat it
|
|
// gently — operators commonly set the var before the user has
|
|
// signed up. The next restart after registration will succeed.
|
|
logger.Warnf(ctx,
|
|
"[bootstrap] %s=%s: user lookup failed (have they signed up yet?): %v",
|
|
bootstrapEnvVar, email, err)
|
|
return
|
|
}
|
|
if user == nil {
|
|
logger.Warnf(ctx,
|
|
"[bootstrap] %s=%s: no matching user (will retry on next restart)",
|
|
bootstrapEnvVar, email)
|
|
return
|
|
}
|
|
if user.IsSystemAdmin {
|
|
logger.Infof(ctx,
|
|
"[bootstrap] %s=%s: user %s is already a system admin (no-op)",
|
|
bootstrapEnvVar, email, user.ID)
|
|
return
|
|
}
|
|
user.IsSystemAdmin = true
|
|
if err := userSvc.UpdateUser(ctx, user); err != nil {
|
|
logger.Warnf(ctx,
|
|
"[bootstrap] %s=%s: failed to promote user %s: %v",
|
|
bootstrapEnvVar, email, user.ID, err)
|
|
return
|
|
}
|
|
logger.Infof(ctx,
|
|
"[bootstrap] promoted user %s (%s) to system admin via %s",
|
|
user.ID, email, bootstrapEnvVar)
|
|
}
|