/* Copyright (C) 2024 Pagefault Games This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package daily import ( "bytes" "context" "crypto/md5" "crypto/rand" "encoding/base64" "encoding/binary" "encoding/json" "fmt" "log" "os" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/pagefaultgames/rogueserver/db" "github.com/robfig/cron/v3" ) const secondsPerDay = 60 * 60 * 24 var ( scheduler = cron.New(cron.WithLocation(time.UTC)) secret []byte ) func Init() error { var err error secret, err = os.ReadFile("secret.key") if err != nil { if !os.IsNotExist(err) { return fmt.Errorf("failed to read daily seed secret: %s", err) } newSecret := make([]byte, 32) _, err := rand.Read(newSecret) if err != nil { return fmt.Errorf("failed to generate daily seed secret: %s", err) } err = os.WriteFile("secret.key", newSecret, 0400) if err != nil { return fmt.Errorf("failed to write daily seed secret: %s", err) } secret = newSecret } seed, err := db.TryAddDailyRun(Seed()) if err != nil { log.Print(err) } log.Printf("Daily Run Seed: %s", seed) _, err = scheduler.AddFunc("@daily", func() { time.Sleep(time.Second) seed, err = db.TryAddDailyRun(Seed()) if err != nil { log.Printf("error while recording new daily: %s", err) } else { log.Printf("Daily Run Seed: %s", seed) } }) if err != nil { return err } scheduler.Start() if os.Getenv("AWS_ENDPOINT_URL_S3") != "" { go func() { for { err = S3SaveMigration() if err != nil { return } } }() } return nil } func Seed() string { return base64.StdEncoding.EncodeToString(deriveSeed(time.Now().UTC())) } func deriveSeed(seedTime time.Time) []byte { day := make([]byte, 8) binary.BigEndian.PutUint64(day, uint64(seedTime.Unix()/secondsPerDay)) hashedSeed := md5.Sum(append(day, secret...)) return hashedSeed[:] } func S3SaveMigration() error { cfg, _ := config.LoadDefaultConfig(context.TODO()) svc := s3.NewFromConfig(cfg, func(o *s3.Options) { o.BaseEndpoint = aws.String(os.Getenv("AWS_ENDPOINT_URL_S3")) }) _, err := svc.CreateBucket(context.Background(), &s3.CreateBucketInput{ Bucket: aws.String(os.Getenv("S3_SYSTEM_BUCKET_NAME")), }) if err != nil { log.Printf("error while creating bucket (already exists?): %s", err) } // retrieve accounts from db accounts, err := db.GetLocalSystemAccounts() if err != nil { return fmt.Errorf("failed to retrieve old accounts: %s", err) } for _, user := range accounts { data, err := db.ReadSystemSaveData(user) if err != nil { continue } username, err := db.FetchUsernameFromUUID(user) if err != nil { continue } json, err := json.Marshal(data) if err != nil { continue } _, err = svc.PutObject(context.Background(), &s3.PutObjectInput{ Bucket: aws.String(os.Getenv("S3_SYSTEM_BUCKET_NAME")), Key: aws.String(username), Body: bytes.NewReader(json), }) if err != nil { log.Printf("error while saving data in S3 for user %s: %s", username, err) continue } err = db.DeleteSystemSaveData(user) if err != nil { log.Printf("failed to delete old save for user %s: %s", username, err) continue } log.Printf("saved data in S3 for user %s", username) } return nil }