refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps

- Merge all files inside openflare-server to the repository root directory.
- Relocate agent, relay, and flared subprojects from internal/ to internal/apps/.
- Combine docker-compose files and update build context paths to root.
- Update GitHub workflows and Dockerfiles to refer to new directories and package names.
- Rewrite Go package imports across all files.
- Resolve database renew test race condition and clean up docs.
This commit is contained in:
ryan
2026-06-19 14:23:29 +08:00
parent 19d476ed7f
commit 63cd906cfc
1064 changed files with 366 additions and 1397 deletions
@@ -0,0 +1,88 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package storage provides upload storage backend operations and migration state.
package storage
import (
"context"
"sync"
"time"
"github.com/Rain-kl/Wavelet/internal/apps/upload/shared"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/storage"
)
// MigrationAccessState captures cached migration maintenance state.
type MigrationAccessState struct {
ReadOnly bool
Target storage.Config
HasTarget bool
TargetErr error
LoadErr error
}
var (
migrationAccessMu sync.RWMutex
migrationAccessCached MigrationAccessState
migrationAccessValid bool
migrationAccessCheckedAt time.Time
)
// ResetMigrationAccessCache clears the in-process migration access cache.
func ResetMigrationAccessCache() {
migrationAccessMu.Lock()
migrationAccessValid = false
migrationAccessMu.Unlock()
}
// LoadMigrationAccessState returns cached migration maintenance state.
func LoadMigrationAccessState(ctx context.Context) MigrationAccessState {
migrationAccessMu.RLock()
if migrationAccessValid && time.Since(migrationAccessCheckedAt) < time.Duration(shared.AccessCacheTTL)*time.Second {
state := migrationAccessCached
migrationAccessMu.RUnlock()
return state
}
migrationAccessMu.RUnlock()
migrationAccessMu.Lock()
defer migrationAccessMu.Unlock()
if migrationAccessValid && time.Since(migrationAccessCheckedAt) < time.Duration(shared.AccessCacheTTL)*time.Second {
return migrationAccessCached
}
migrationAccessCached = buildMigrationAccessState(ctx)
migrationAccessValid = true
migrationAccessCheckedAt = time.Now()
return migrationAccessCached
}
func buildMigrationAccessState(ctx context.Context) MigrationAccessState {
execution, ok, err := LatestMigrationExecution(ctx)
if err != nil {
return MigrationAccessState{LoadErr: err, ReadOnly: true}
}
if !ok {
return MigrationAccessState{}
}
state := MigrationAccessState{
ReadOnly: execution.Status != model.TaskExecutionStatusSucceeded,
}
if execution.Status == model.TaskExecutionStatusSucceeded {
return state
}
target, err := ParseMigrationTargetConfig(ctx, []byte(execution.Payload))
if err != nil {
state.TargetErr = err
return state
}
state.Target = target
state.HasTarget = true
return state
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package storage
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/Rain-kl/Wavelet/internal/db"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/storage"
"gorm.io/gorm"
)
// StorageMigrationTask is the Asynq task name for storage migration.
const StorageMigrationTask = "storage:migrate"
// LatestMigrationExecution returns the most recent storage migration task execution.
func LatestMigrationExecution(ctx context.Context) (*model.TaskExecution, bool, error) {
var execution model.TaskExecution
err := db.DB(ctx).
Where("task_type = ?", StorageMigrationTask).
Order("id DESC").
First(&execution).Error
if err == nil {
return &execution, true, nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, false, err
}
return nil, false, nil
}
// ParseMigrationTargetConfig parses and validates a storage migration target payload.
func ParseMigrationTargetConfig(ctx context.Context, payload []byte) (storage.Config, error) {
if strings.TrimSpace(string(payload)) == "" {
return storage.Config{}, errors.New("storage migration target payload is required")
}
var raw struct {
Target json.RawMessage `json:"target"`
}
if err := json.Unmarshal(payload, &raw); err != nil {
return storage.Config{}, fmt.Errorf("parse storage migration payload envelope: %w", err)
}
if len(raw.Target) == 0 {
return storage.Config{}, errors.New("storage migration target payload is required")
}
var targetBytes []byte
var targetStr string
if err := json.Unmarshal(raw.Target, &targetStr); err == nil {
targetBytes = []byte(targetStr)
} else {
targetBytes = raw.Target
}
var target storage.Config
if err := json.Unmarshal(targetBytes, &target); err != nil {
return storage.Config{}, fmt.Errorf("parse target storage config: %w", err)
}
current, err := storage.LoadConfig(ctx)
if err != nil {
return storage.Config{}, fmt.Errorf("load active storage config: %w", err)
}
target = storage.MergeMaskedSecrets(target, current)
if err := storage.ValidateConfig(target); err != nil {
return storage.Config{}, fmt.Errorf("validate target storage config: %w", err)
}
return target, nil
}
// NormalizeMigrationPayload validates and normalizes a storage migration payload.
func NormalizeMigrationPayload(ctx context.Context, payload []byte) ([]byte, storage.Config, error) {
target, err := ParseMigrationTargetConfig(ctx, payload)
if err != nil {
return nil, storage.Config{}, err
}
type storageMigrationPayload struct {
Target storage.Config `json:"target"`
}
normalized, err := json.Marshal(storageMigrationPayload{Target: target})
if err != nil {
return nil, storage.Config{}, fmt.Errorf("marshal storage migration payload: %w", err)
}
return normalized, target, nil
}
@@ -0,0 +1,31 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package storage
import (
"context"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/storage"
"github.com/Rain-kl/Wavelet/pkg/logger"
)
// ReadOnly checks if the storage system is in read-only maintenance mode.
func ReadOnly(ctx context.Context) bool {
state := LoadMigrationAccessState(ctx)
if state.LoadErr != nil {
logger.ErrorF(ctx, "读取存储维护状态失败: %v", state.LoadErr)
return true
}
return state.ReadOnly
}
// OpenStoredObject opens a stored upload object from the active storage backend.
func OpenStoredObject(ctx context.Context, upload *model.Upload) (*storage.Object, error) {
_, backend, err := storage.Active(ctx)
if err != nil {
return nil, err
}
return backend.Get(ctx, upload.FilePath)
}