Files
Luminary/cmd/luminary-recover/main.go
T
renjue 76ba500417
CI / docker (push) Successful in 2m4s
Initial commit: Luminary AI Gateway
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress,
ingress key governance, monitoring, and security controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 21:46:16 +08:00

157 lines
4.2 KiB
Go

// luminary-recover: emergency recovery CLI (reset password, clear IP rules, restart).
// Intended for docker exec when admin login is locked out.
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"github.com/glebarez/sqlite"
"github.com/rose_cat707/luminary/internal/auth"
"github.com/rose_cat707/luminary/internal/model"
"github.com/rose_cat707/luminary/internal/settings"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func main() {
os.Exit(run())
}
func run() int {
dataDir := flag.String("data-dir", envOr("LUMINARY_DATA", "./data"), "directory containing luminary.db")
deprecatedConfig := flag.String("config", "", "deprecated: ignored; use -data-dir / LUMINARY_DATA")
username := flag.String("username", "", "admin username (default: admin)")
password := flag.String("password", "", "new admin password (default: admin123)")
doAll := flag.Bool("all", false, "reset password, clear IP rules and sessions, then restart")
doPassword := flag.Bool("reset-password", false, "reset admin password")
doIP := flag.Bool("clear-ip", false, "delete all IP rules")
doSessions := flag.Bool("clear-sessions", false, "delete all admin sessions")
doRestart := flag.Bool("restart", false, "send SIGTERM to PID 1 to restart server (clears login cooldown)")
flag.Parse()
if *deprecatedConfig != "" {
fmt.Fprintln(os.Stderr, "warning: -config is deprecated and ignored; use -data-dir / LUMINARY_DATA")
}
if !*doAll && !*doPassword && !*doIP && !*doSessions && !*doRestart {
fmt.Fprintln(os.Stderr, "usage: luminary-recover --all [--password NEW] [--restart]")
fmt.Fprintln(os.Stderr, " luminary-recover --reset-password --clear-ip --clear-sessions [--restart]")
flag.PrintDefaults()
return 2
}
user := *username
pass := *password
if user == "" || pass == "" {
legacyUser, legacyPass := settings.LegacyAdminCredentials()
if user == "" {
user = legacyUser
}
if pass == "" {
pass = legacyPass
}
}
if *doAll {
*doPassword = true
*doIP = true
*doSessions = true
*doRestart = true
}
dbPath := filepath.Join(*dataDir, "luminary.db")
db, err := openDB(dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "open database: %v\n", err)
return 1
}
if *doPassword {
if pass == "" {
fmt.Fprintln(os.Stderr, "password is required (use --password)")
return 1
}
if err := resetPassword(db, user, pass); err != nil {
fmt.Fprintf(os.Stderr, "reset password: %v\n", err)
return 1
}
fmt.Printf("admin password reset for user %q\n", user)
}
if *doIP {
res := db.Exec("DELETE FROM ip_rules")
if res.Error != nil {
fmt.Fprintf(os.Stderr, "clear IP rules: %v\n", res.Error)
return 1
}
fmt.Printf("cleared %d IP rule(s)\n", res.RowsAffected)
}
if *doSessions {
res := db.Exec("DELETE FROM admin_sessions")
if res.Error != nil {
fmt.Fprintf(os.Stderr, "clear sessions: %v\n", res.Error)
return 1
}
fmt.Printf("cleared %d admin session(s)\n", res.RowsAffected)
}
if *doRestart {
fmt.Println("sending SIGTERM to PID 1 (restart server to clear in-memory login cooldown)...")
if err := syscall.Kill(1, syscall.SIGTERM); err != nil {
fmt.Fprintf(os.Stderr, "restart: %v (you may need to restart the container manually)\n", err)
return 1
}
}
fmt.Println("done")
return 0
}
func openDB(path string) (*gorm.DB, error) {
dsn := path + "?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil, err
}
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxOpenConns(1)
return db, nil
}
func resetPassword(db *gorm.DB, username, password string) error {
hash, err := auth.HashPassword(password)
if err != nil {
return err
}
var user model.AdminUser
err = db.Where("username = ?", username).First(&user).Error
if err == gorm.ErrRecordNotFound {
return db.Create(&model.AdminUser{
Username: username,
PasswordHash: hash,
CreatedAt: time.Now(),
}).Error
}
if err != nil {
return err
}
return db.Model(&user).Update("password_hash", hash).Error
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}