Files
freephilx 2e6256fb6a feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
2026-09-08 22:47:35 +08:00

127 lines
3.9 KiB
Go

package bootstrap
import (
"bytes"
"os"
"path/filepath"
"testing"
"github.com/kazeyukiro/3m-ui/backend/internal/auth"
"github.com/kazeyukiro/3m-ui/backend/internal/database/models"
)
func TestInitializePreservesCredentialsAndConfiguration(t *testing.T) {
root := t.TempDir()
t.Setenv("THREE_M_UI_DATA_DIR", filepath.Join(root, "data"))
t.Setenv("THREE_M_UI_ADMIN_PASSWORD", "")
t.Setenv("THREE_M_UI_ADMIN_USERNAME", "")
t.Setenv("THREE_M_UI_PORT", "8099")
path := filepath.Join(root, "config", "config.yaml")
first, err := Initialize(path)
if err != nil {
t.Fatal(err)
}
firstDB, _ := first.DB.DB()
defer firstDB.Close()
if !first.Created || first.Username != "admin" || len(first.Password) < 24 || first.Password == "admin" {
t.Fatal("expected a newly generated administrator")
}
if first.Config.Server.Port != 8099 || first.Config.JWT.Secret == first.Config.Security.CredentialKey {
t.Fatal("configuration defaults or independent secrets are incorrect")
}
var user models.User
if err := first.DB.First(&user).Error; err != nil {
t.Fatal(err)
}
if !user.MustChangePassword || !auth.CheckPasswordHash(first.Password, user.PasswordHash) {
t.Fatal("administrator must use a password hash and require a password change")
}
raw, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(raw, []byte(first.Password)) {
t.Fatal("password must not be written to config")
}
info, _ := os.Stat(path)
if info.Mode().Perm() != 0600 {
t.Fatalf("config mode = %o", info.Mode().Perm())
}
var printed bytes.Buffer
first.PrintCredentials(&printed)
if !bytes.Contains(printed.Bytes(), []byte(first.Password)) {
t.Fatal("initial credentials must be available once")
}
firstDB.Close()
t.Setenv("THREE_M_UI_ADMIN_PASSWORD", "replacement-must-not-apply")
second, err := Initialize(path)
if err != nil {
t.Fatal(err)
}
secondDB, _ := second.DB.DB()
defer secondDB.Close()
if second.Created || second.Password != "" {
t.Fatal("restart recreated administrator")
}
printed.Reset()
second.PrintCredentials(&printed)
if printed.Len() != 0 {
t.Fatal("restart displayed existing credentials")
}
after, _ := os.ReadFile(path)
if !bytes.Equal(raw, after) {
t.Fatal("restart changed persisted configuration")
}
var persisted models.User
second.DB.First(&persisted)
if persisted.PasswordHash != user.PasswordHash {
t.Fatal("restart changed password hash")
}
}
func TestInitializeRejectsBadConfigWithoutReplacingIt(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yaml")
bad := []byte("server: [broken\n")
if err := os.WriteFile(path, bad, 0600); err != nil {
t.Fatal(err)
}
if _, err := Initialize(path); err == nil {
t.Fatal("malformed existing config must fail")
}
after, _ := os.ReadFile(path)
if !bytes.Equal(bad, after) {
t.Fatal("invalid config was overwritten")
}
}
func TestExplicitPasswordAndReset(t *testing.T) {
root := t.TempDir()
t.Setenv("THREE_M_UI_DATA_DIR", filepath.Join(root, "data"))
t.Setenv("THREE_M_UI_ADMIN_USERNAME", "operator")
t.Setenv("THREE_M_UI_ADMIN_PASSWORD", "explicit-bootstrap-password")
initialized, err := Initialize(filepath.Join(root, "config.yaml"))
if err != nil {
t.Fatal(err)
}
sqlDB, _ := initialized.DB.DB()
defer sqlDB.Close()
if initialized.Username != "operator" || initialized.Password != "explicit-bootstrap-password" {
t.Fatal("explicit bootstrap credentials ignored")
}
password, err := auth.GeneratePassword()
if err != nil {
t.Fatal(err)
}
if err := auth.ResetAdminPassword(initialized.DB, password); err != nil {
t.Fatal(err)
}
var user models.User
initialized.DB.First(&user)
if !auth.CheckPasswordHash(password, user.PasswordHash) || user.SessionVersion != 2 || !user.MustChangePassword {
t.Fatal("reset must invalidate sessions and require password change")
}
if err := auth.ResetAdminPassword(initialized.DB, ""); err == nil {
t.Fatal("empty reset must not restore admin/admin")
}
}