mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Add admin API for content reload
This commit is contained in:
+22
-5
@@ -93,6 +93,10 @@ func main() {
|
||||
grpcOctoURL := flag.String("grpc.octo-url", "", "Octo CDN base URL passed to lunar-tear (default: derived from cdn.public-addr)")
|
||||
grpcAuthURL := flag.String("grpc.auth-url", "", "auth server base URL passed to lunar-tear (default: derived from auth.listen)")
|
||||
|
||||
// admin webhook is opt-in; empty leaves lunar-tear's own default in place
|
||||
// (the listener still only binds if LUNAR_ADMIN_TOKEN is set in the env).
|
||||
adminListen := flag.String("admin.listen", "", "lunar-tear admin webhook listen address (host:port). Empty = leave default; webhook only binds when LUNAR_ADMIN_TOKEN is set in the env.")
|
||||
|
||||
noColor := flag.Bool("no-color", false, "disable colored output")
|
||||
flag.Parse()
|
||||
|
||||
@@ -139,11 +143,7 @@ func main() {
|
||||
label: "grpc",
|
||||
color: colorYellow,
|
||||
cmd: exec.CommandContext(ctx, filepath.Join("bin", "lunar-tear"+ext),
|
||||
"--listen", *grpcListen,
|
||||
"--public-addr", *grpcPublicAddr,
|
||||
"--db", *grpcDB,
|
||||
"--octo-url", *grpcOctoURL,
|
||||
"--auth-url", *grpcAuthURL,
|
||||
grpcArgs(*grpcListen, *grpcPublicAddr, *grpcDB, *grpcOctoURL, *grpcAuthURL, *adminListen)...,
|
||||
),
|
||||
},
|
||||
}
|
||||
@@ -200,3 +200,20 @@ func prefixLines(wg *sync.WaitGroup, prefix string, r io.Reader) {
|
||||
fmt.Printf("%s%s\n", prefix, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
// grpcArgs assembles the argv for the lunar-tear subprocess. The admin flag
|
||||
// is appended only when --admin.listen was supplied so we don't override
|
||||
// lunar-tear's own default when the operator hasn't opted in.
|
||||
func grpcArgs(listen, publicAddr, db, octoURL, authURL, adminListen string) []string {
|
||||
args := []string{
|
||||
"--listen", listen,
|
||||
"--public-addr", publicAddr,
|
||||
"--db", db,
|
||||
"--octo-url", octoURL,
|
||||
"--auth-url", authURL,
|
||||
}
|
||||
if adminListen != "" {
|
||||
args = append(args, "--admin-listen", adminListen)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"lunar-tear/server/internal/runtime"
|
||||
)
|
||||
|
||||
// startAdmin spins up the admin webhook used by external content tools to
|
||||
// trigger an in-place re-read of assets/release/20240404193219.bin.e.
|
||||
//
|
||||
// Authentication: Bearer token via the LUNAR_ADMIN_TOKEN environment variable.
|
||||
// If LUNAR_ADMIN_TOKEN is unset or empty the listener does not bind at all
|
||||
// (fail closed), so a fresh deploy never exposes an unauthenticated endpoint.
|
||||
//
|
||||
// The default --admin-listen is 127.0.0.1:8082 so the webhook is only
|
||||
// reachable via loopback unless the operator opts in by binding to 0.0.0.0.
|
||||
func startAdmin(listen string, holder *runtime.Holder) {
|
||||
token := os.Getenv("LUNAR_ADMIN_TOKEN")
|
||||
if token == "" {
|
||||
log.Println("[admin] disabled (no LUNAR_ADMIN_TOKEN set)")
|
||||
return
|
||||
}
|
||||
expected := []byte("Bearer " + token)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/admin/master-data/reload", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
got := []byte(r.Header.Get("Authorization"))
|
||||
if len(got) != len(expected) || subtle.ConstantTimeCompare(got, expected) != 1 {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if err := holder.Reload(); err != nil {
|
||||
log.Printf("[admin] master-data reload failed: %v", err)
|
||||
http.Error(w, "master-data reload failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("[admin] master-data reloaded by %s", r.RemoteAddr)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"ok":true}`))
|
||||
})
|
||||
|
||||
log.Printf("[admin] webhook listener on %s (token-gated)", listen)
|
||||
go func() {
|
||||
if err := http.ListenAndServe(listen, mux); err != nil {
|
||||
log.Printf("[admin] webhook listener failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -6,10 +6,8 @@ import (
|
||||
"strconv"
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gacha"
|
||||
"lunar-tear/server/internal/interceptor"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/service"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
@@ -40,27 +38,7 @@ func startGRPC(
|
||||
store.UserRepository
|
||||
store.SessionRepository
|
||||
},
|
||||
questEngine *questflow.QuestHandler,
|
||||
gachaHandler *gacha.GachaHandler,
|
||||
gachaEntries []store.GachaCatalogEntry,
|
||||
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
|
||||
loginBonusCatalog *masterdata.LoginBonusCatalog,
|
||||
characterViewerCatalog *masterdata.CharacterViewerCatalog,
|
||||
shopCatalog *masterdata.ShopCatalog,
|
||||
costumeCatalog *masterdata.CostumeCatalog,
|
||||
omikujiCatalog *masterdata.OmikujiCatalog,
|
||||
weaponCatalog *masterdata.WeaponCatalog,
|
||||
exploreCatalog *masterdata.ExploreCatalog,
|
||||
gimmickCatalog *masterdata.GimmickCatalog,
|
||||
characterBoardCatalog *masterdata.CharacterBoardCatalog,
|
||||
partsCatalog *masterdata.PartsCatalog,
|
||||
characterRebirthCatalog *masterdata.CharacterRebirthCatalog,
|
||||
companionCatalog *masterdata.CompanionCatalog,
|
||||
materialCatalog *masterdata.MaterialCatalog,
|
||||
consumableItemCatalog *masterdata.ConsumableItemCatalog,
|
||||
gameConfig *masterdata.GameConfig,
|
||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
||||
holder *runtime.Holder,
|
||||
) *grpc.Server {
|
||||
lis, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
@@ -74,33 +52,7 @@ func startGRPC(
|
||||
grpc.UnknownServiceHandler(interceptor.UnknownService),
|
||||
)
|
||||
|
||||
registerServices(grpcServer,
|
||||
publicAddr,
|
||||
octoURL,
|
||||
authURL,
|
||||
userStore,
|
||||
questEngine,
|
||||
gachaHandler,
|
||||
gachaEntries,
|
||||
cageOrnamentCatalog,
|
||||
loginBonusCatalog,
|
||||
characterViewerCatalog,
|
||||
shopCatalog,
|
||||
costumeCatalog,
|
||||
omikujiCatalog,
|
||||
weaponCatalog,
|
||||
exploreCatalog,
|
||||
gimmickCatalog,
|
||||
characterBoardCatalog,
|
||||
partsCatalog,
|
||||
characterRebirthCatalog,
|
||||
companionCatalog,
|
||||
materialCatalog,
|
||||
consumableItemCatalog,
|
||||
gameConfig,
|
||||
sideStoryCatalog,
|
||||
bigHuntCatalog,
|
||||
)
|
||||
registerServices(grpcServer, publicAddr, octoURL, authURL, userStore, holder)
|
||||
|
||||
reflection.Register(grpcServer)
|
||||
|
||||
@@ -124,66 +76,46 @@ func registerServices(
|
||||
store.UserRepository
|
||||
store.SessionRepository
|
||||
},
|
||||
questEngine *questflow.QuestHandler,
|
||||
gachaHandler *gacha.GachaHandler,
|
||||
gachaEntries []store.GachaCatalogEntry,
|
||||
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
|
||||
loginBonusCatalog *masterdata.LoginBonusCatalog,
|
||||
characterViewerCatalog *masterdata.CharacterViewerCatalog,
|
||||
shopCatalog *masterdata.ShopCatalog,
|
||||
costumeCatalog *masterdata.CostumeCatalog,
|
||||
omikujiCatalog *masterdata.OmikujiCatalog,
|
||||
weaponCatalog *masterdata.WeaponCatalog,
|
||||
exploreCatalog *masterdata.ExploreCatalog,
|
||||
gimmickCatalog *masterdata.GimmickCatalog,
|
||||
characterBoardCatalog *masterdata.CharacterBoardCatalog,
|
||||
partsCatalog *masterdata.PartsCatalog,
|
||||
characterRebirthCatalog *masterdata.CharacterRebirthCatalog,
|
||||
companionCatalog *masterdata.CompanionCatalog,
|
||||
materialCatalog *masterdata.MaterialCatalog,
|
||||
consumableItemCatalog *masterdata.ConsumableItemCatalog,
|
||||
gameConfig *masterdata.GameConfig,
|
||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
||||
holder *runtime.Holder,
|
||||
) {
|
||||
pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr)
|
||||
pubPort, _ := strconv.Atoi(pubPortStr)
|
||||
|
||||
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries))
|
||||
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(holder))
|
||||
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL))
|
||||
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
||||
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL))
|
||||
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
|
||||
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine))
|
||||
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler))
|
||||
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterGiftServiceServer(srv, service.NewGiftServiceServer(userStore, userStore))
|
||||
pb.RegisterGamePlayServiceServer(srv, service.NewGameplayServiceServer())
|
||||
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, gimmickCatalog))
|
||||
pb.RegisterQuestServiceServer(srv, service.NewQuestServiceServer(userStore, userStore, questEngine))
|
||||
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterQuestServiceServer(srv, service.NewQuestServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterNotificationServiceServer(srv, service.NewNotificationServiceServer(userStore, userStore))
|
||||
pb.RegisterCageOrnamentServiceServer(srv, service.NewCageOrnamentServiceServer(userStore, userStore, cageOrnamentCatalog, questEngine.Granter))
|
||||
pb.RegisterCageOrnamentServiceServer(srv, service.NewCageOrnamentServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterDeckServiceServer(srv, service.NewDeckServiceServer(userStore, userStore))
|
||||
pb.RegisterFriendServiceServer(srv, service.NewFriendServiceServer(userStore, userStore))
|
||||
pb.RegisterLoginBonusServiceServer(srv, service.NewLoginBonusServiceServer(userStore, userStore, loginBonusCatalog))
|
||||
pb.RegisterLoginBonusServiceServer(srv, service.NewLoginBonusServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterNaviCutInServiceServer(srv, service.NewNaviCutInServiceServer(userStore, userStore))
|
||||
pb.RegisterContentsStoryServiceServer(srv, service.NewContentsStoryServiceServer(userStore, userStore))
|
||||
pb.RegisterDokanServiceServer(srv, service.NewDokanServiceServer(userStore, userStore))
|
||||
pb.RegisterPortalCageServiceServer(srv, service.NewPortalCageServiceServer(userStore, userStore))
|
||||
pb.RegisterCharacterViewerServiceServer(srv, service.NewCharacterViewerServiceServer(userStore, userStore, characterViewerCatalog))
|
||||
pb.RegisterCharacterViewerServiceServer(srv, service.NewCharacterViewerServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterMissionServiceServer(srv, service.NewMissionServiceServer(userStore, userStore))
|
||||
pb.RegisterShopServiceServer(srv, service.NewShopServiceServer(userStore, userStore, shopCatalog, questEngine.Granter))
|
||||
pb.RegisterCostumeServiceServer(srv, service.NewCostumeServiceServer(userStore, userStore, costumeCatalog, gameConfig))
|
||||
pb.RegisterShopServiceServer(srv, service.NewShopServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterCostumeServiceServer(srv, service.NewCostumeServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterMovieServiceServer(srv, service.NewMovieServiceServer(userStore, userStore))
|
||||
pb.RegisterOmikujiServiceServer(srv, service.NewOmikujiServiceServer(userStore, userStore, omikujiCatalog))
|
||||
pb.RegisterWeaponServiceServer(srv, service.NewWeaponServiceServer(userStore, userStore, weaponCatalog, gameConfig))
|
||||
pb.RegisterExploreServiceServer(srv, service.NewExploreServiceServer(userStore, userStore, exploreCatalog))
|
||||
pb.RegisterCharacterBoardServiceServer(srv, service.NewCharacterBoardServiceServer(userStore, userStore, characterBoardCatalog))
|
||||
pb.RegisterPartsServiceServer(srv, service.NewPartsServiceServer(userStore, userStore, partsCatalog, gameConfig))
|
||||
pb.RegisterCharacterServiceServer(srv, service.NewCharacterServiceServer(userStore, userStore, characterRebirthCatalog, gameConfig))
|
||||
pb.RegisterCompanionServiceServer(srv, service.NewCompanionServiceServer(userStore, userStore, companionCatalog, gameConfig))
|
||||
pb.RegisterMaterialServiceServer(srv, service.NewMaterialServiceServer(userStore, userStore, materialCatalog, gameConfig))
|
||||
pb.RegisterConsumableItemServiceServer(srv, service.NewConsumableItemServiceServer(userStore, userStore, consumableItemCatalog, gameConfig))
|
||||
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, sideStoryCatalog))
|
||||
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine))
|
||||
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter))
|
||||
pb.RegisterOmikujiServiceServer(srv, service.NewOmikujiServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterWeaponServiceServer(srv, service.NewWeaponServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterExploreServiceServer(srv, service.NewExploreServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterCharacterBoardServiceServer(srv, service.NewCharacterBoardServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterPartsServiceServer(srv, service.NewPartsServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterCharacterServiceServer(srv, service.NewCharacterServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterCompanionServiceServer(srv, service.NewCompanionServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterMaterialServiceServer(srv, service.NewMaterialServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterConsumableItemServiceServer(srv, service.NewConsumableItemServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, holder))
|
||||
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, holder))
|
||||
}
|
||||
|
||||
@@ -9,30 +9,30 @@ import (
|
||||
"syscall"
|
||||
|
||||
"lunar-tear/server/internal/database"
|
||||
"lunar-tear/server/internal/gacha"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/masterdata/memorydb"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store/sqlite"
|
||||
)
|
||||
|
||||
const masterDataPath = "assets/release/20240404193219.bin.e"
|
||||
|
||||
func main() {
|
||||
listen := flag.String("listen", "0.0.0.0:443", "gRPC listen address (host:port)")
|
||||
publicAddr := flag.String("public-addr", "127.0.0.1:443", "externally-reachable host:port advertised to clients")
|
||||
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
||||
octoURL := flag.String("octo-url", "", "Octo CDN base URL the client will use for assets (e.g. http://10.0.2.2:8080)")
|
||||
authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)")
|
||||
adminListen := flag.String("admin-listen", "127.0.0.1:8082", "admin webhook listen address (host:port). Loopback by default; only binds when LUNAR_ADMIN_TOKEN is set.")
|
||||
flag.Parse()
|
||||
|
||||
if *octoURL == "" {
|
||||
log.Fatalf("--octo-url is required (e.g. http://10.0.2.2:8080)")
|
||||
}
|
||||
|
||||
if err := memorydb.Init("assets/release/20240404193219.bin.e"); err != nil {
|
||||
log.Fatalf("load master data: %v", err)
|
||||
holder, err := runtime.NewHolder(masterDataPath)
|
||||
if err != nil {
|
||||
log.Fatalf("init master data: %v", err)
|
||||
}
|
||||
log.Printf("master data loaded (%d tables)", memorydb.TableCount())
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
@@ -44,158 +44,11 @@ func main() {
|
||||
defer db.Close()
|
||||
log.Printf("database opened: %s", *dbPath)
|
||||
|
||||
gameConfig, err := masterdata.LoadGameConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("load game config: %v", err)
|
||||
}
|
||||
log.Printf("game config loaded (goldId=%d, skipTicketId=%d, rebirthGold=%d)",
|
||||
gameConfig.ConsumableItemIdForGold, gameConfig.ConsumableItemIdForQuestSkipTicket, gameConfig.CharacterRebirthConsumeGold)
|
||||
|
||||
partsCatalog, err := masterdata.LoadPartsCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load parts catalog: %v", err)
|
||||
}
|
||||
log.Printf("parts catalog loaded: %d parts, %d rarities", len(partsCatalog.PartsById), len(partsCatalog.RarityByRarityType))
|
||||
|
||||
questCatalog, err := masterdata.LoadQuestCatalog(partsCatalog)
|
||||
if err != nil {
|
||||
log.Fatalf("load quest catalog: %v", err)
|
||||
}
|
||||
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
|
||||
userStore := sqlite.New(db, gametime.Now)
|
||||
|
||||
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load gacha catalog: %v", err)
|
||||
}
|
||||
log.Printf("gacha catalog loaded: %d entries", len(gachaEntries))
|
||||
grpcServer := startGRPC(*listen, *publicAddr, *octoURL, *authURL, userStore, holder)
|
||||
|
||||
gachaPool, err := masterdata.LoadGachaPool()
|
||||
if err != nil {
|
||||
log.Fatalf("load gacha pool: %v", err)
|
||||
}
|
||||
log.Printf("gacha pool loaded: costumes=%d rarities, weapons=%d rarities, materials=%d",
|
||||
len(gachaPool.CostumesByRarity), len(gachaPool.WeaponsByRarity), len(gachaPool.Materials))
|
||||
|
||||
shopCatalog, err := masterdata.LoadShopCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load shop catalog: %v", err)
|
||||
}
|
||||
log.Printf("shop catalog loaded: %d items, %d content groups, %d exchange shops",
|
||||
len(shopCatalog.Items), len(shopCatalog.Contents), len(shopCatalog.ExchangeShopCells))
|
||||
|
||||
gachaPool.BuildShopFeatured(shopCatalog)
|
||||
gachaPool.PruneUnpairedCostumes()
|
||||
gachaPool.BuildFeaturedMapping(gachaEntries)
|
||||
gachaPool.BuildBannerPools(gachaEntries)
|
||||
masterdata.EnrichCatalogPromotions(gachaEntries, gachaPool)
|
||||
|
||||
dupExchange, err := masterdata.LoadDupExchange()
|
||||
if err != nil {
|
||||
log.Fatalf("load dup exchange: %v", err)
|
||||
}
|
||||
dupAdded, err := masterdata.EnrichDupExchange(dupExchange, gachaPool)
|
||||
if err != nil {
|
||||
log.Fatalf("enrich dup exchange: %v", err)
|
||||
}
|
||||
log.Printf("dup exchange loaded: %d entries (%d derived from limit-break materials)", len(dupExchange), dupAdded)
|
||||
|
||||
gachaHandler := gacha.NewGachaHandler(gachaPool, gameConfig, questHandler.Granter, medalInfo, dupExchange)
|
||||
|
||||
conditionResolver, err := masterdata.LoadConditionResolver()
|
||||
if err != nil {
|
||||
log.Fatalf("load condition resolver: %v", err)
|
||||
}
|
||||
|
||||
cageOrnamentCatalog := masterdata.LoadCageOrnamentCatalog()
|
||||
loginBonusCatalog := masterdata.LoadLoginBonusCatalog()
|
||||
characterViewerCatalog := masterdata.LoadCharacterViewerCatalog(conditionResolver)
|
||||
omikujiCatalog := masterdata.LoadOmikujiCatalog()
|
||||
|
||||
materialCatalog, err := masterdata.LoadMaterialCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load material catalog: %v", err)
|
||||
}
|
||||
log.Printf("material catalog loaded: %d materials", len(materialCatalog.All))
|
||||
|
||||
consumableItemCatalog, err := masterdata.LoadConsumableItemCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load consumable item catalog: %v", err)
|
||||
}
|
||||
log.Printf("consumable item catalog loaded: %d items", len(consumableItemCatalog.All))
|
||||
|
||||
costumeCatalog, err := masterdata.LoadCostumeCatalog(materialCatalog)
|
||||
if err != nil {
|
||||
log.Fatalf("load costume catalog: %v", err)
|
||||
}
|
||||
log.Printf("costume catalog loaded: %d costumes, %d materials, %d rarity curves", len(costumeCatalog.Costumes), len(costumeCatalog.Materials), len(costumeCatalog.ExpByRarity))
|
||||
|
||||
weaponCatalog, err := masterdata.LoadWeaponCatalog(materialCatalog)
|
||||
if err != nil {
|
||||
log.Fatalf("load weapon catalog: %v", err)
|
||||
}
|
||||
log.Printf("weapon catalog loaded: %d weapons, %d materials, %d enhance configs", len(weaponCatalog.Weapons), len(weaponCatalog.Materials), len(weaponCatalog.ExpByEnhanceId))
|
||||
|
||||
exploreCatalog, err := masterdata.LoadExploreCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load explore catalog: %v", err)
|
||||
}
|
||||
log.Printf("explore catalog loaded: %d explores, %d grade assets", len(exploreCatalog.Explores), len(exploreCatalog.GradeAssets))
|
||||
|
||||
gimmickCatalog, err := masterdata.LoadGimmickCatalog(conditionResolver)
|
||||
if err != nil {
|
||||
log.Fatalf("load gimmick catalog: %v", err)
|
||||
}
|
||||
|
||||
characterBoardCatalog, err := masterdata.LoadCharacterBoardCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load character board catalog: %v", err)
|
||||
}
|
||||
log.Printf("character board catalog loaded: %d panels, %d boards", len(characterBoardCatalog.PanelById), len(characterBoardCatalog.BoardById))
|
||||
|
||||
characterRebirthCatalog, err := masterdata.LoadCharacterRebirthCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load character rebirth catalog: %v", err)
|
||||
}
|
||||
log.Printf("character rebirth catalog loaded: %d characters", len(characterRebirthCatalog.StepGroupByCharacterId))
|
||||
|
||||
companionCatalog, err := masterdata.LoadCompanionCatalog()
|
||||
if err != nil {
|
||||
log.Fatalf("load companion catalog: %v", err)
|
||||
}
|
||||
log.Printf("companion catalog loaded: %d companions, %d categories", len(companionCatalog.CompanionById), len(companionCatalog.GoldCostByCategory))
|
||||
|
||||
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
||||
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
||||
|
||||
grpcServer := startGRPC(
|
||||
*listen,
|
||||
*publicAddr,
|
||||
*octoURL,
|
||||
*authURL,
|
||||
userStore,
|
||||
questHandler,
|
||||
gachaHandler,
|
||||
gachaEntries,
|
||||
cageOrnamentCatalog,
|
||||
loginBonusCatalog,
|
||||
characterViewerCatalog,
|
||||
shopCatalog,
|
||||
costumeCatalog,
|
||||
omikujiCatalog,
|
||||
weaponCatalog,
|
||||
exploreCatalog,
|
||||
gimmickCatalog,
|
||||
characterBoardCatalog,
|
||||
partsCatalog,
|
||||
characterRebirthCatalog,
|
||||
companionCatalog,
|
||||
materialCatalog,
|
||||
consumableItemCatalog,
|
||||
gameConfig,
|
||||
sideStoryCatalog,
|
||||
bigHuntCatalog,
|
||||
)
|
||||
startAdmin(*adminListen, holder)
|
||||
|
||||
<-ctx.Done()
|
||||
log.Println("shutting down...")
|
||||
|
||||
+58
-17
@@ -32,13 +32,14 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
IP string `json:"ip"`
|
||||
Device string `json:"device"`
|
||||
Detail string `json:"detail"`
|
||||
Summary string `json:"summary"`
|
||||
GRPCPort int `json:"grpc_port,omitempty"`
|
||||
CDNPort int `json:"cdn_port,omitempty"`
|
||||
AuthPort int `json:"auth_port,omitempty"`
|
||||
IP string `json:"ip"`
|
||||
Device string `json:"device"`
|
||||
Detail string `json:"detail"`
|
||||
Summary string `json:"summary"`
|
||||
GRPCPort int `json:"grpc_port,omitempty"`
|
||||
CDNPort int `json:"cdn_port,omitempty"`
|
||||
AuthPort int `json:"auth_port,omitempty"`
|
||||
AdminPort int `json:"admin_port,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -47,10 +48,13 @@ const (
|
||||
defaultAuthPort = 3000
|
||||
)
|
||||
|
||||
// ports.Admin is opt-in: 0 means the admin webhook is not configured by the
|
||||
// wizard at all. Other ports always get a default if unset.
|
||||
type ports struct {
|
||||
GRPC int
|
||||
CDN int
|
||||
Auth int
|
||||
GRPC int
|
||||
CDN int
|
||||
Auth int
|
||||
Admin int
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -59,6 +63,7 @@ func main() {
|
||||
grpcPort := flag.Int("grpc-port", defaultGRPCPort, "gRPC server port")
|
||||
cdnPort := flag.Int("cdn-port", defaultCDNPort, "CDN server port")
|
||||
authPort := flag.Int("auth-port", defaultAuthPort, "auth server port")
|
||||
adminPort := flag.Int("admin-port", 0, "admin webhook port (0 = disabled). Bound on 127.0.0.1; only takes effect when LUNAR_ADMIN_TOKEN is set.")
|
||||
flag.Parse()
|
||||
|
||||
flagSet := map[string]bool{}
|
||||
@@ -80,10 +85,10 @@ func main() {
|
||||
|
||||
ip, cfg, firstRun := resolveIP(*preferSaved)
|
||||
|
||||
p := resolvePorts(flagSet, *grpcPort, *cdnPort, *authPort, cfg)
|
||||
p := resolvePorts(flagSet, *grpcPort, *cdnPort, *authPort, *adminPort, cfg)
|
||||
savedPorts := portsFromConfig(cfg)
|
||||
|
||||
if !firstRun && (p.GRPC != savedPorts.GRPC || p.CDN != savedPorts.CDN || p.Auth != savedPorts.Auth) {
|
||||
if !firstRun && (p.GRPC != savedPorts.GRPC || p.CDN != savedPorts.CDN || p.Auth != savedPorts.Auth || p.Admin != savedPorts.Admin) {
|
||||
if !warnPortChange(savedPorts, p) {
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -92,6 +97,7 @@ func main() {
|
||||
cfg.GRPCPort = p.GRPC
|
||||
cfg.CDNPort = p.CDN
|
||||
cfg.AuthPort = p.Auth
|
||||
cfg.AdminPort = p.Admin
|
||||
saveConfig(cfg)
|
||||
|
||||
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Width(14)
|
||||
@@ -101,6 +107,9 @@ func main() {
|
||||
fmt.Printf(" %s %s\n", labelStyle.Render("Game server:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.GRPC)))
|
||||
fmt.Printf(" %s %s\n", labelStyle.Render("CDN:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.CDN)))
|
||||
fmt.Printf(" %s %s\n", labelStyle.Render("Auth:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.Auth)))
|
||||
if p.Admin > 0 {
|
||||
fmt.Printf(" %s %s\n", labelStyle.Render("Admin webhook:"), addrStyle.Render(fmt.Sprintf("127.0.0.1:%d", p.Admin)))
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
if firstRun || *setupOnly {
|
||||
@@ -477,6 +486,22 @@ func warnPortChange(old, new ports) bool {
|
||||
}
|
||||
return hlStyle.Render(fmt.Sprintf(" %-7s %d → %d", label+":", oldP, newP))
|
||||
}
|
||||
// Admin formatting handles the disabled (0) state since the port is
|
||||
// opt-in and we don't want to display "0" to the user.
|
||||
adminLine := func(oldP, newP int) (string, bool) {
|
||||
switch {
|
||||
case oldP == 0 && newP == 0:
|
||||
return "", false
|
||||
case oldP == 0 && newP != 0:
|
||||
return hlStyle.Render(fmt.Sprintf(" %-7s disabled → %d", "Admin:", newP)), true
|
||||
case oldP != 0 && newP == 0:
|
||||
return hlStyle.Render(fmt.Sprintf(" %-7s %d → disabled", "Admin:", oldP)), true
|
||||
case oldP == newP:
|
||||
return dimStyle.Render(fmt.Sprintf(" %-7s %d (unchanged)", "Admin:", oldP)), true
|
||||
default:
|
||||
return hlStyle.Render(fmt.Sprintf(" %-7s %d → %d", "Admin:", oldP, newP)), true
|
||||
}
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("\n")
|
||||
@@ -487,7 +512,12 @@ func warnPortChange(old, new ports) bool {
|
||||
b.WriteString(portLine("CDN", old.CDN, new.CDN))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(portLine("Auth", old.Auth, new.Auth))
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString("\n")
|
||||
if line, show := adminLine(old.Admin, new.Admin); show {
|
||||
b.WriteString(line)
|
||||
b.WriteString("\n")
|
||||
}
|
||||
b.WriteString("\n")
|
||||
b.WriteString(dimStyle.Render(" Your APK was patched for the old ports. You may need to re-patch."))
|
||||
b.WriteString("\n\n")
|
||||
fmt.Print(b.String())
|
||||
@@ -821,7 +851,7 @@ func loadConfig() (config, error) {
|
||||
}
|
||||
|
||||
func portsFromConfig(cfg config) ports {
|
||||
p := ports{GRPC: cfg.GRPCPort, CDN: cfg.CDNPort, Auth: cfg.AuthPort}
|
||||
p := ports{GRPC: cfg.GRPCPort, CDN: cfg.CDNPort, Auth: cfg.AuthPort, Admin: cfg.AdminPort}
|
||||
if p.GRPC == 0 {
|
||||
p.GRPC = defaultGRPCPort
|
||||
}
|
||||
@@ -831,10 +861,11 @@ func portsFromConfig(cfg config) ports {
|
||||
if p.Auth == 0 {
|
||||
p.Auth = defaultAuthPort
|
||||
}
|
||||
// Admin is opt-in: leave 0 = disabled.
|
||||
return p
|
||||
}
|
||||
|
||||
func resolvePorts(flagSet map[string]bool, grpcFlag, cdnFlag, authFlag int, saved config) ports {
|
||||
func resolvePorts(flagSet map[string]bool, grpcFlag, cdnFlag, authFlag, adminFlag int, saved config) ports {
|
||||
resolve := func(name string, flagVal, savedVal, defaultVal int) int {
|
||||
if flagSet[name] {
|
||||
return flagVal
|
||||
@@ -848,6 +879,9 @@ func resolvePorts(flagSet map[string]bool, grpcFlag, cdnFlag, authFlag int, save
|
||||
GRPC: resolve("grpc-port", grpcFlag, saved.GRPCPort, defaultGRPCPort),
|
||||
CDN: resolve("cdn-port", cdnFlag, saved.CDNPort, defaultCDNPort),
|
||||
Auth: resolve("auth-port", authFlag, saved.AuthPort, defaultAuthPort),
|
||||
// defaultVal=0 keeps admin opt-in: never enabled unless --admin-port
|
||||
// is passed or a non-zero value was previously saved.
|
||||
Admin: resolve("admin-port", adminFlag, saved.AdminPort, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,13 +908,20 @@ func launchDev(ip string, p ports) {
|
||||
runQuiet(exec.Command("go", "build", "-o", devBin, "./cmd/dev"), "build dev")
|
||||
}).Run()
|
||||
|
||||
cmd := exec.Command(devBin,
|
||||
devArgs := []string{
|
||||
"--grpc.listen", fmt.Sprintf("0.0.0.0:%d", p.GRPC),
|
||||
"--grpc.public-addr", fmt.Sprintf("%s:%d", ip, p.GRPC),
|
||||
"--cdn.listen", fmt.Sprintf("0.0.0.0:%d", p.CDN),
|
||||
"--cdn.public-addr", fmt.Sprintf("%s:%d", ip, p.CDN),
|
||||
"--auth.listen", fmt.Sprintf("0.0.0.0:%d", p.Auth),
|
||||
)
|
||||
}
|
||||
// Bind admin on loopback only — the wizard is for local dev, and the
|
||||
// webhook should never be exposed to the LAN by accident. Operators who
|
||||
// want a different bind can run cmd/dev directly with --admin.listen.
|
||||
if p.Admin > 0 {
|
||||
devArgs = append(devArgs, "--admin.listen", fmt.Sprintf("127.0.0.1:%d", p.Admin))
|
||||
}
|
||||
cmd := exec.Command(devBin, devArgs...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
|
||||
Reference in New Issue
Block a user